Compare commits

...

45 Commits

Author SHA1 Message Date
fujie
56a6ddd422 feat(smart-mind-map-tool): initial release v1.0.0 with adaptive Rich UI 2026-03-04 23:18:21 +08:00
fujie
eda495e55f refactor(feature-request): simplify template and add plugin dropdown
- Convert Plugin Name from text input to dropdown with all plugins
- Add Tool type option to match bug report template
- Merge Alternatives Considered into Additional Information section
- Combine Motivation fields into single Motivation & Use Case field
- Reduce from 7 to 5 key fields for better UX
- Keep all critical information for feature evaluation
- Consistent structure with bug report template
2026-03-04 05:03:54 +08:00
fujie
3642058292 refactor(bug-report): simplify template for better UX
- Reduce from 17 to 9 key fields
- Merge Expected/Actual Behavior into single Description field
- Combine environment and browser into Operating System & Container field
- Move screenshots to Additional Information section
- Keep core fields essential for bug reproduction (plugin, version, OS, steps)
- Add collapsible debug information guide with frontend/server log instructions
- Improve user experience by reducing form fatigue
- Maintain all critical debugging information
2026-03-04 05:02:04 +08:00
fujie
5b0464dcdd feat(bug-report): add plugin dropdown selector
- Convert Plugin Name from text input to dropdown menu
- Include all current plugins (Actions, Filters, Pipelines, Pipes, Tools)
- Add 'Other / Not Listed' option with fallback text field
- Reduces typos and improves issue categorization accuracy
- Easier plugin-specific issue tracking and statistics
2026-03-04 04:58:12 +08:00
fujie
2aff7f1bf4 fix(bug-report): correct plugin types and environment info
- Change 'Tool (Copilot SDK)' to 'Tool' (Copilot SDK is a Pipe plugin)
- Rename Environment Details field
- Include OS and container/deployment info (Docker, Docker Compose, native, etc)
- Update checklist to reflect container/deployment requirements
- Keep OpenWebUI version as required field
- Ensures proper environment context for issue reproduction
2026-03-04 04:57:24 +08:00
fujie
ba0d63930e refactor(bug-report): simplify to OS info only
- Change Environment Information to Operating System field
- Keep only OS details (Windows/macOS/Linux versions)
- Simplify checklist requirement from full environment to OS info
- Maintains essential debugging information without overhead
2026-03-04 04:55:18 +08:00
fujie
a1568de67b enhance(bug-report): improve issue template with tool type and better logging guidance
- Add Tool (Copilot SDK) as plugin type option
- Add Environment Information field (OS, hardware, deployment)
  - Platform, device type, GPU/hardware, Python version, Docker, deployment method
- Add detailed Debug Logs & Troubleshooting guide with:
  - Frontend console logs (with steps to enable Plugin Debug Output)
  - Server-side logs (docker logs or local terminal)
- Separate Frontend Console Logs field with debug output instructions
- Add Server-Side Logs field for backend error tracking
- Add Screenshots field with guidance on what to capture
- Enhanced Checklist with environment and logs requirements
- Improves issue triage and faster bug resolution
2026-03-04 04:54:53 +08:00
fujie
f4a38a7906 feat: add issue templates for bug reports and feature requests
- Add bug_report.yml template with plugin-specific fields
- Add feature_request.yml template for enhancement suggestions
- Add config.yml to customize issue creation experience
- Templates help standardize issue reporting and improve triage
2026-03-04 04:51:40 +08:00
fujie
2e6c61737f fix(workflow): only trigger release on actual version changes
- Remove doc-only change trigger to prevent unnecessary releases
- Release should only be created when plugin versions actually change
- Keeps manual workflow_dispatch and tag-based releases intact
2026-03-04 04:45:32 +08:00
fujie
c1e9aca5dc feat(pipes/github-copilot-sdk): add strict file link validation rules
- Add Strict Link Validity Rule (CRITICAL) forbidding fabricated URLs
- Implement Failure Handling for non-successful publish operations
- Establish Allowed Link Formats (Whitelist) with exact patterns
- Define Invalid Link Examples (Forbidden) including /c/... and relative paths
- Add Auto-Correction Rule requiring discard of non-whitelisted links
- Enhance URL Format guidance to emphasize verbatim copying without modification
- Add Workspace Visibility Hint (#10) for workspace file inspection context
2026-03-04 04:14:48 +08:00
Fu-Jie
6f700fe610 Fix capitalization in README description
Corrected capitalization of 'OpenWebUI' to 'open-webui'.
2026-03-04 04:11:16 +08:00
fujie
3927e384cc docs(skill): add open-terminal project to source-code-analyzer 2026-03-04 02:20:24 +08:00
fujie
e1dac2219e fix(stats): stabilize Top6 version badges and fix Excel version display [skip release]
- replace rank-based p{idx}_version usage with per-post static version badge generation

- set Export to Excel version badge to v0.3.7 in home README and README_CN
2026-03-04 01:59:31 +08:00
fujie
9436364b9a docs(readme): restore version badge for Copilot SDK; ci: add skip-release support and README link injection [skip release]
- add static v0.9.1 badge to Copilot SDK Star Features heading

- inject plugin README link before each embedded v*.md in release body

- skip release workflow when commit message contains [skip release]
2026-03-04 01:34:24 +08:00
fujie
e7b1ff4c54 docs(readme): clean up Star Features badges and fix Copilot SDK stats badge keys
- remove all Market badges from Star Features headings

- replace invalid p0_dl/p0_vw placeholders with correct per-post MD5-keyed Gist badges

- remove GitHub Copilot SDK row from Top 6 table (not in ranked stats yet)
2026-03-04 01:20:35 +08:00
fujie
c4ff4fea7e ci(release): reorder release body and improve commits format
- move v*.md release notes to top of release body

- commits now show subject (bold) + body lines per Conventional Commits best practice
2026-03-04 01:19:34 +08:00
fujie
32afc3286e ci(release): fix release notes format
- remove file path header (### plugins/...) from embedded release note content

- exclude _CN.md files to avoid Chinese/English mixed output

- remove Documentation Changes file list and Additional Documentation Files section
2026-03-04 01:06:54 +08:00
fujie
3e8b15af46 ci(release): include documentation content in release body
- embed changed v*.md release note content into generated GitHub release notes

- keep additional doc changes visible as file list in release output

- refine v0.9.1 EN/CN release notes to include only actual updated/fixed items
2026-03-04 00:57:18 +08:00
fujie
658f37baa6 ci(release): optimize plugin release workflow
- detect both version changes and plugin/docs release-note updates

- broaden push path filters and include documentation change summary in release notes

- harden worktree temp handling, remove duplicate extraction, and skip tag creation on tag-triggered runs
2026-03-04 00:52:07 +08:00
fujie
c65ba57553 chore(release): finalize github-copilot-sdk v0.9.1 prep
- sync bilingual plugin READMEs, docs mirrors, and root highlight sections for v0.9.1

- add comprehensive EN/CN release notes covering features, fixes, docs, and i18n updates

- enforce full-coverage release-note rule in release-prep skill for future releases
2026-03-04 00:47:48 +08:00
fujie
7c17dbbe23 style(pipes): unify entity name to 'Agent' across all languages
- Replaced 'Assistant' and its translations with 'Agent' in all translation dictionaries (EN, ZH, JA, KO, FR, DE, IT, ES, RU, VI, ID)
- Consistent with the previous change in Chinese locales.
2026-03-04 00:17:19 +08:00
fujie
c6279240b9 fix(pipes): fix mcp tool filtering and force-enable autonomous web search
- Fix issue where mcp tool filtering logic (function_name_filter_list) in admin backend caused all tools to be hidden due to ID prefix mismatch
- Force enable web_search tool for Copilot Agent regardless of UI toggles, providing full autonomy for search-related intents
- Updated README and version to v0.9.1
2026-03-04 00:11:28 +08:00
fujie
a8a324500a Refactor code structure for improved readability and maintainability 2026-03-03 19:36:53 +08:00
fujie
369e8c900c fix(stats): restore dynamic badges and update community statistics
- Recover dynamic Shields.io badges in README by restoring missing Gist ID 'db3d95687075a880af6f1fba76d679c6'.
- Add 'tool' to DOWNLOADABLE_TYPES and implement TYPE_ALIASES for normalization (mapping 'tools' to 'tool').
- Update community statistics and Ranking list (Top 6) based on latest marketplace data.
- Refactor openwebui_stats.py with 100% English comments and enhanced user ID auto-resolution.
- Verify Smart Mind Map (#1) and other top plugins maintain correct sorting.
2026-03-03 19:29:34 +08:00
fujie
83e317a335 docs(skills): align doc-mirror-sync docs-only guidance
- Add docs-only sync mode rules (no version bump, no release actions).

- Add EN-only and EN+ZH sync command examples.

- Keep .github and .gemini skill instructions in sync.
2026-02-28 23:24:26 +08:00
fujie
c28c3c837b docs(openwebui-skills-manager): prioritize official marketplace install
- Update bilingual plugin README to use official marketplace as default install path.

- Keep manual paste installation as fallback and sync docs mirrors.

- Keep version unchanged at 0.2.1.
2026-02-28 23:20:30 +08:00
fujie
701ea0b18f fix(openwebui-skills-manager): replace broken install example URLs
- Replace non-existent anthropics skills paths with valid xlsx/docx examples.

- Keep English and Chinese README examples synchronized.
2026-02-28 23:13:16 +08:00
fujie
eb79bc9633 fix(tools): release openwebui-skills-manager v0.2.1
- Add GitHub skills-directory auto-discovery for install_skill batch install from tree root URLs.

- Harden language detection with frontend-first fallback (__event_call__ + timeout), then headers/profile.

- Bump plugin/docs versions to 0.2.1 and sync bilingual README/docs/index entries.
2026-02-28 23:06:08 +08:00
fujie
0c7d427b93 chore: cleanup debug files and update plugin configuration 2026-02-28 16:39:41 +08:00
fujie
07bc5f027e feat(skills-manager): add openwebui_id and fix tool type sync bug 2026-02-28 16:14:38 +08:00
fujie
701fc3e906 docs(skills-manager): add skill installation examples for single and batch URLs 2026-02-28 15:53:04 +08:00
fujie
d392af66c9 chore: rename update_pipe.py to deploy_pipe.py to reflect its creation capabilities and update skills reference 2026-02-28 15:29:46 +08:00
fujie
67cf86fb26 fix(pipes): yield artifacts string directly to stream flow avoiding emitter replacement 2026-02-28 15:21:28 +08:00
fujie
fe98b0e007 fix(pipes): fallback artifacts mode to using direct view_url iframe to bypass openwebui srcdoc sandbox cdn crash 2026-02-28 14:58:56 +08:00
fujie
3236d19e28 fix(pipes): restore corrupted isinstance check + add frontend console debug tracing 2026-02-28 14:35:53 +08:00
fujie
354c1eee6b chore(pipes): add debug tracing for pending_embeds lifecycle 2026-02-28 14:30:38 +08:00
fujie
6b3eb8064b fix(pipes): align artifacts internal array type tracking with stream loops 2026-02-28 14:17:05 +08:00
fujie
f32e90e182 feat(pipes): automate artifacts html generation via emitter message append 2026-02-28 13:48:53 +08:00
fujie
8001ab18ee feat(pipes): update richui mode to send raw HTML content instead of an iframe tag 2026-02-28 13:11:03 +08:00
fujie
dcfde9c0dc fix(pipes): revert artifacts iframe to barebone structure to avoid OpenWebUI markdown parsing issues 2026-02-28 13:06:10 +08:00
fujie
dbcf7421ea feat(pipes): simplify richui iframe tag to give full styling control to openwebui emitter 2026-02-28 13:04:10 +08:00
fujie
1705baf976 feat(pipes): unrestrict richui iframe height and apply generous sandbox policies 2026-02-28 13:01:38 +08:00
fujie
8e8d478ece fix(pipes): inject explicit sandbox policies to artifacts iframe to mitigate client-side block 2026-02-28 12:57:22 +08:00
fujie
acc9cd7ff2 fix(pipes): clarify ANTI-INLINE rule to allow iframe output and hardcode iframe shadow 2026-02-28 12:37:49 +08:00
fujie
e4582c3197 feat(pipes): optimize richui vs artifacts selection and enforce fullscreen button 2026-02-28 12:23:10 +08:00
116 changed files with 8411 additions and 10159 deletions

71
.agent/skills/README.md Normal file
View File

@@ -0,0 +1,71 @@
# 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`
---
## Release Pipeline Skills
These four skills form a complete release pipeline and are designed to be used in sequence:
```
release-prep → pr-submitter → pr-reviewer → release-finalizer
(prepare) (push & PR) (respond to review) (merge & close issue)
```
- **release-prep**
- Purpose: Full release preparation — version sync across 7+ files, bilingual release notes creation, consistency check, and commit.
- Entry: `release-prep/SKILL.md`
- **pr-submitter**
- Purpose: Shell-escape-safe PR submission — writes body to temp file, validates sections, pushes branch, creates PR via `gh pr create --body-file`.
- Entry: `pr-submitter/SKILL.md`
- **pr-reviewer**
- Purpose: Fetch PR review comments, categorize feedback, implement fixes, commit and push, reply to reviewers.
- Entry: `pr-reviewer/SKILL.md`
- **release-finalizer**
- Purpose: Merge release PR to main with proper commit message, auto-link and close related issues, post closing messages.
- Entry: `release-finalizer/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.

View 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.

View File

@@ -0,0 +1,50 @@
---
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`.
## Docs-Only Mode (No Release Changes)
Use this mode when the request is "only sync docs".
- Only update documentation mirror files under `docs/plugins/**`.
- Do **not** bump plugin version.
- Do **not** modify plugin code (`plugins/**.py`) unless explicitly requested.
- Do **not** update root badges/dates for release.
- Do **not** run release preparation steps.
## Workflow
1. Identify changed READMEs.
2. Copy content to corresponding mirror paths.
3. Update version badges in `docs/plugins/{type}/index.md`.
## Commands
### Sync all mirrors (EN + ZH)
```bash
python .github/skills/doc-mirror-sync/scripts/sync.py
```
### Sync only one plugin (EN only)
```bash
cp plugins/<type>/<name>/README.md docs/plugins/<type>/<name>.md
```
### Sync only one plugin (EN + ZH)
```bash
cp plugins/<type>/<name>/README.md docs/plugins/<type>/<name>.md
cp plugins/<type>/<name>/README_CN.md docs/plugins/<type>/<name>.zh.md
```
## Notes
- If asked for English-only update, sync only `README.md` -> `.md` mirror.
- If both languages are requested, sync both `README.md` and `README_CN.md`.
- After syncing, verify git diff only contains docs file changes.

View 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()

View 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>`.

View 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

View 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!"

View 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

View 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)

View 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

View 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.

View 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])

View 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

View 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.

View 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

View 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

View 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])

View File

@@ -0,0 +1,180 @@
---
name: pr-reviewer
description: Fetches PR review comments, analyzes requested changes, implements fixes, commits and pushes the resolution. Use after a reviewer has left comments on an open PR to close the feedback loop efficiently.
---
# PR Reviewer
## Overview
This skill automates the response cycle for code review. When a reviewer leaves comments on a Pull Request, this skill fetches all pending feedback, categorizes issues by severity, implements fixes, and submits a follow-up commit with appropriate review response comments.
## Prerequisites
- An open PR exists with pending review comments
- The local branch matches the PR's head branch
- `gh` CLI is authenticated
---
## Workflow
### Step 1 — Fetch Review State
Retrieve all review comments and overall review status:
```bash
# Get overall review decisions
PAGER=cat GH_PAGER=cat gh pr view <PR-NUMBER> --json reviews,reviewDecision,headRefName \
--jq '{decision: .reviewDecision, reviews: [.reviews[] | {author: .author.login, state: .state, body: .body}]}'
# Get inline code comments (specific line comments)
PAGER=cat GH_PAGER=cat gh api repos/Fu-Jie/openwebui-extensions/pulls/<PR-NUMBER>/comments \
--jq '[.[] | {path: .path, line: .line, body: .body, author: .user.login, id: .id}]'
# Get general issue comments
PAGER=cat GH_PAGER=cat gh issue view <PR-NUMBER> --comments --json comments \
--jq '[.comments[] | {author: .author.login, body: .body}]'
```
Confirm the current local branch matches the PR head:
```bash
git branch --show-current
```
If mismatched, checkout the correct branch first.
### Step 2 — Categorize Review Feedback
Group feedback into categories:
| Category | Examples | Action |
|----------|---------|--------|
| **Code Bug** | Logic error, incorrect variable, broken condition | Fix code immediately |
| **Style / Formatting** | Indentation, naming convention, missing blank line | Fix code |
| **Documentation** | Missing i18n key, wrong version in README, typo | Fix docs |
| **Design Question** | Suggestion to restructure, alternative approach | Discuss with user before implementing |
| **Nitpick / Optional** | Minor style preferences reviewer marked as optional | Fix if quick; document if skipped |
| **Blocking** | Reviewer explicitly blocks merge | Must fix before proceeding |
Present the full categorized list to the user and confirm the resolution plan.
### Step 3 — Implement Fixes
For each accepted fix:
1. Read the affected file at the commented line for context:
```bash
sed -n '<line-5>,<line+10>p' <file-path>
```
2. Apply the fix using appropriate file edit tools
3. After editing, verify the specific area looks correct
**For code changes that might affect behavior:**
- Check if tests exist: `ls tests/test_*.py`
- If tests exist, run them: `python -m pytest tests/ -v`
**For documentation fixes:**
- If modifying README.md, check if `docs/` mirror needs the same fix
- Apply the same fix to both locations
### Step 4 — Run Consistency Checks
After all fixes are applied:
```bash
# Version consistency (if any version files were touched)
python3 scripts/check_version_consistency.py
# Quick syntax check for Python files
python3 -m py_compile plugins/{type}/{name}/{name}.py && echo "✅ Syntax OK"
```
### Step 5 — Stage and Commit
Create a new commit (do NOT amend if the branch has already been pushed, to avoid force-push):
```bash
git add -A
git status
```
Draft a Conventional Commits message for the fixup:
Format: `fix(scope): address review feedback`
Body should list what was fixed, referencing reviewer concerns:
```
fix(github-copilot-sdk): address review feedback from @reviewer
- Fix X per review comment on line Y of file Z
- Update README to clarify auth requirement
- Correct edge case in _parse_mcp_servers logic
```
```bash
git commit -m "<fixup commit message>"
```
### Step 6 — Push the Fix Commit
```bash
git push origin $(git branch --show-current)
```
**Force-push policy:**
- Use `git push` (non-force) by default
- Only use `git push --force-with-lease` if:
1. The user explicitly requests it, AND
2. The only change is an amended commit squash (cosmetic, no logic change)
3. Never use `--force` (without `--lease`)
### Step 7 — Respond to Reviewers
For each addressed review comment, post a reply:
```bash
# Reply to inline comment
gh api repos/Fu-Jie/openwebui-extensions/pulls/<PR-NUMBER>/comments/<COMMENT-ID>/replies \
-X POST -f body="Fixed in commit <SHORT-SHA>. <Brief explanation of what was changed.>"
# General comment to summarize all fixes
gh issue comment <PR-NUMBER> --body "All review feedback addressed in commit <SHORT-SHA>:
- Fixed: <item 1>
- Fixed: <item 2>
Ready for re-review. 🙏"
```
### Step 8 — Re-Request Review (Optional)
If the reviewer had submitted a `CHANGES_REQUESTED` review, request a new review after fixes:
```bash
PAGER=cat GH_PAGER=cat gh api repos/Fu-Jie/openwebui-extensions/pulls/<PR-NUMBER>/requested_reviewers \
-X POST -f reviewers[]='<reviewer-login>'
```
---
## Decision Guide
### When NOT to implement a suggestion immediately
- **Design questions**: "Should this be a separate class?" — Present to user for decision
- **Optional nitpicks**: Reviewer marked as `nit:` — Ask user if they want to include it
- **Large refactors**: If fix would require changing >50 lines, propose a separate follow-up issue instead
### When to ask the user before proceeding
- Any fix involving behavioral changes to plugin logic
- Renaming Valve keys (breaking change — requires migration notes)
- Changes that affect the bilingual release notes already committed
---
## Anti-Patterns to Avoid
- ❌ Do NOT `git commit --amend` on a pushed commit without user approval for force-push
- ❌ Do NOT silently skip a reviewer's comment; always acknowledge it (implement or explain why not)
- ❌ Do NOT use `--force` (only `--force-with-lease` when absolutely necessary)
- ❌ Do NOT make unrelated changes in the fixup commit; keep scope focused on review feedback
- ❌ Do NOT respond to reviewer comments in Chinese if the PR language context is English

View File

@@ -0,0 +1,194 @@
---
name: pr-submitter
description: Submits a feature branch as a Pull Request with a validated, properly formatted bilingual PR body. Handles shell-escape-safe body writing via temp files. Use after release-prep has committed all changes.
---
# PR Submitter
## Overview
This skill handles the final step of pushing a feature branch and creating a validated Pull Request on GitHub. Its primary purpose is to avoid the shell-escaping pitfalls (backticks, special characters in `gh pr create --body`) by always writing the PR body to a **temp file** first.
## Prerequisites
- All changes are committed (use `release-prep` skill first)
- The `gh` CLI is authenticated (`gh auth status`)
- Current branch is NOT `main` or `master`
---
## Workflow
### Step 0 — Initialize Temp Directory (Project-Based)
For all temporary files, use the project's `.temp/` directory instead of system `/tmp`:
```bash
# Create temp directory if it doesn't exist
mkdir -p .temp
```
**Why**: All temporary files stay within the project workspace, avoiding system `/tmp` pollution and better aligning with OpenWebUI workspace isolation principles.
### Step 1 — Pre-Flight Checks
Run these checks before any push:
```bash
# 1. Confirm not on protected branch
git branch --show-current
# 2. Verify there are commits to push
git log origin/$(git branch --show-current)..HEAD --oneline 2>/dev/null || echo "No remote tracking branch yet"
# 3. Check gh CLI auth
gh auth status
```
If any check fails, stop and report clearly.
### Step 2 — Collect PR Metadata
Gather:
- **PR Title**: Must follow Conventional Commits format, English only (e.g., `feat(github-copilot-sdk): release v0.8.0 with conditional tool filtering`)
- **Target base branch**: Default is `main`
- **Plugin name + version** (to build body sections)
- **Key changes** (reuse from release-prep or the latest What's New section)
### Step 3 — Build PR Body File (Shell-Escape-Safe)
**Always write the body to a temp file in `.temp/` directory.** Never embed multi-line markdown with special characters directly in a shell command.
```bash
cat > .temp/pr_body.md << 'HEREDOC'
## Summary
Brief one-sentence description of what this PR accomplishes.
## Changes
### New Features
- Feature 1 description
- Feature 2 description
### Bug Fixes
- Fix 1 description
## Plugin Version
- `PluginName` bumped to `vX.X.X`
## Documentation
- README.md / README_CN.md updated
- docs/ mirrors synced
## Testing
- [ ] Tested locally in OpenWebUI
- [ ] i18n validated (all language keys present)
- [ ] Version consistency check passed (`python3 scripts/check_version_consistency.py`)
---
## 变更摘要(中文)
简要描述本次 PR 的改动内容。
### 新功能
- 功能1描述
- 功能2描述
### 问题修复
- 修复1描述
HEREDOC
```
**Critical rules for the body file:**
- Use `<< 'HEREDOC'` (quoted heredoc) to prevent variable expansion
- Keep all backticks literal — they are safe inside a heredoc
- Paths like `/api/v1/files/` are safe too since heredoc doesn't interpret them as commands
### Step 4 — Validate PR Body
Before submitting, verify the body file contains expected sections:
```bash
# Check key sections exist
grep -q "## Summary" .temp/pr_body.md && echo "✅ Summary" || echo "❌ Summary missing"
grep -q "## Changes" .temp/pr_body.md && echo "✅ Changes" || echo "❌ Changes missing"
grep -q "## 变更摘要" .temp/pr_body.md && echo "✅ CN Section" || echo "❌ CN Section missing"
# Preview the body
cat .temp/pr_body.md
```
Ask the user to confirm the body content before proceeding.
### Step 5 — Push Branch
```bash
git push -u origin $(git branch --show-current)
```
If push is rejected (non-fast-forward), report to user and ask whether to force-push. **Do NOT force-push without explicit confirmation.**
### Step 6 — Create Pull Request
```bash
gh pr create \
--base main \
--head $(git branch --show-current) \
--title "<PR title from Step 2>" \
--body-file .temp/pr_body.md
```
Always use `--body-file`, never `--body` with inline markdown.
### Step 7 — Verify PR Creation
```bash
PAGER=cat GH_PAGER=cat gh pr view --json number,url,title,body --jq '{number: .number, url: .url, title: .title, body_preview: .body[:200]}'
```
Confirm:
- PR number and URL
- Title matches intended Conventional Commits format
- Body preview includes key sections (not truncated/corrupted)
If the body appears corrupted (empty sections, missing backtick content), use edit:
```bash
gh pr edit <PR-NUMBER> --body-file /tmp/pr_body.md
```
### Step 8 — Cleanup
```bash
rm -f .temp/pr_body.md
```
**Note**: The `.temp/` directory itself is preserved for reuse; only the individual PR body file is deleted. To fully clean up: `rm -rf .temp/`
Report final PR URL to the user.
---
## Shell-Escape Safety Rules
| Risk | Safe Approach |
|------|--------------|
| Backticks in `--body` | Write to file, use `--body-file` |
| Paths like `/api/...` | Safe in heredoc; risky in inline `--body` |
| Newlines in `--body` | File-based only |
| `$variable` expansion | Use `<< 'HEREDOC'` (quoted) |
| Double quotes in body | Safe in heredoc file |
| Temp file storage | Use `.temp/` dir, not `/tmp` |
| Cleanup after use | Always delete temp file (keep dir) |
---
## Anti-Patterns to Avoid
- ❌ Never use `--body "..."` with multi-line content directly in shell command
- ❌ Never interpolate variables directly into heredoc without quoting the delimiter
- ❌ Never force-push (`--force`) without explicit user confirmation
- ❌ Never target `main` as the source branch (only as base)
- ❌ Never skip the body validation step — a PR with empty body is worse than a delayed PR

View File

@@ -0,0 +1,208 @@
---
name: release-finalizer
description: Merges a release PR, associates it with resolved issues, replies to issue reporters, and closes issues. Use after PR review is complete and ready for merge. Closes the release cycle.
---
# Release Finalizer
## Overview
This skill completes the final step of the release cycle: merging the release PR to `main`, replying to all related issues with solutions, and automatically closing them using GitHub's issue linking mechanism.
## Prerequisites
- The PR is in `OPEN` state and ready to merge
- All status checks have passed (CI green)
- All review feedback has been addressed
- The PR relates to one or more GitHub issues (either in PR description or through commits)
---
## Workflow
### Step 1 — Pre-Merge Verification
Verify that the PR is ready:
```bash
PAGER=cat GH_PAGER=cat gh pr view <PR-NUMBER> --json state,statusCheckRollup,reviewDecision
```
Checklist:
-`state` is `OPEN`
-`statusCheckRollup` all have `conclusion: SUCCESS`
-`reviewDecision` is `APPROVED` or empty (no blocking reviews)
If any check fails, **do NOT merge**. Report the issue to the user.
### Step 2 — Identify Related Issues
Issues can be linked to a PR in multiple ways. Check the PR description and commit messages for keywords:
```bash
PAGER=cat GH_PAGER=cat gh pr view <PR-NUMBER> --json body,commits
```
Look for patterns like:
- `Closes #XX`, `Fixes #XX`, `Resolves #XX` (in description or commit bodies)
- `#XX` mentioned as "related to" or "addresses"
**Manual input**: If issue links are not in the PR, ask the user which issue(s) this PR resolves.
Extract all issue numbers into a list: `[#48, #52, ...]`
### Step 3 — Select Merge Strategy
Offer the user three options:
| Strategy | Git Behavior | Use Case |
|----------|-------------|----------|
| **Squash** | All commits squashed into one commit on main | Clean history, recommended for release PRs |
| **Rebase** | Linear history, no merge commit | Preserve commit granularity |
| **Merge** | Merge commit created | Preserve full PR context |
**Recommendation for release PRs**: Use `--squash` to create a single clean commit.
If user doesn't specify, default to `--squash`.
### Step 4 — Prepare Merge Commit Message
If using `--squash`, craft a single comprehensive commit message:
**Format** (Conventional Commits + Github linking):
```
type(scope): description
- Bullet point 1
- Bullet point 2
Closes #48
Closes #52
```
The `Closes #XX` keyword tells GitHub to automatically close those issues when the commit lands on `main`.
Example:
```
feat(pipes,filters): release Copilot SDK Pipe v0.8.0 and Files Filter v0.1.3
- Implement P1~P4 conditional tool filtering system
- Fix file publishing reliability across all storage backends
- Add strict file URL validation
- Update bilingual documentation
Closes #48
```
### Step 5 — Execute Merge
```bash
gh pr merge <PR-NUMBER> \
--squash \
--delete-branch \
-m "type(scope): description" \
-b "- Bullet 1\n- Bullet 2\n\nCloses #48"
```
**Key flags:**
- `--squash`: Squash commits (recommended for releases)
- `--delete-branch`: Delete the feature branch after merge
- `-m`: Commit subject
- `-b`: Commit body (supports `\n` for newlines)
Confirm the merge is successful; GitHub will automatically close related issues with `Closes #XX` keyword.
### Step 6 — Verify Auto-Close
GitHub automatically closes issues when a commit with `Closes #XX` lands on the default branch (`main`).
To verify:
```bash
PAGER=cat GH_PAGER=cat gh issue view <ISSUE-NUMBER> --json state
```
Should show `state: CLOSED`.
### Step 7 — Post Closing Message (Optional but Recommended)
For better UX, manually post a summary comment to **each issue** before it auto-closes (since auto-close happens silently):
```bash
gh issue comment <ISSUE-NUMBER> --body "
This has been fixed in PR #<PR-NUMBER>, which is now merged to main.
**Solution Summary:**
- <Key fix 1>
- <Key fix 2>
The fix will be available in the next plugin release. Thank you for reporting! ⭐
"
```
### Step 8 — (Optional) Regenerate Release Notes
If the merge revealed any final tweaks to release notes:
```bash
# Re-export release notes from merged commit
git log --oneline -1 <merged-commit-sha>
```
If needed, create a follow-up PR with doc polish (do NOT force-push the merged commit).
---
## Merge Strategy Decision Tree
```
Is this a patch/hotfix release?
├─ YES → Use --squash
└─ NO → Multi-feature release?
├─ YES → Use --squash (cleaner history)
└─ NO → Preserve detail?
├─ YES → Use --rebase
└─ NO → Use --merge (preserve PR context)
```
---
## Issue Auto-Close Keywords
These keywords in commit/PR messages will auto-close issues when merged to `main`:
- `Closes #XX`
- `Fixes #XX`
- `Resolves #XX`
- `close #XX` (case-insensitive)
- `fix #XX`
- `resolve #XX`
**Important**: The keyword must be on the **final commit that lands on** `main`. For squash merges, it must be in the squash commit message body.
---
## Anti-Patterns to Avoid
- ❌ Do NOT merge if any status checks are PENDING or FAILED
- ❌ Do NOT merge if there are blocking reviews (reviewDecision: `CHANGES_REQUESTED`)
- ❌ Do NOT merge without verifying the Conventional Commits format in the merge message
- ❌ Do NOT merge without including `Closes #XX` keywords for all related issues
- ❌ Do NOT assume issues will auto-close silently — post a courtesy comment first
- ❌ Do NOT delete the branch if it might be needed for cherry-pick or hotfixes later
---
## Troubleshooting
### Issue did not auto-close after merge
- Verify the `Closes #XX` keyword is in the **final commit message** (use `git log` to check)
- Ensure the commit is on the `main` branch
- GitHub sometimes takes a few seconds to process; refresh the issue page
### Multiple issues to close
- List all in separate `Closes #XX` lines in the commit body
- Each one will be independently auto-closed
### Want to close issue without merge?
- Use `gh issue close <ISSUE-NUMBER>` manually
- Only recommended if the PR was manually reverted or deemed invalid

View File

@@ -0,0 +1,157 @@
---
name: release-prep
description: Orchestrates the full release preparation flow for a plugin — version sync across 7+ files, bilingual release notes creation, and commit message drafting. Use before submitting a PR. Does NOT push or create a PR; that is handled by pr-submitter.
---
# Release Prep
## Overview
This skill drives the complete pre-PR release pipeline. It enforces the repository rule that every release must synchronize the version number and changelog across **at least 7 locations** before a commit is created.
## Scope
This skill covers:
1. Version sync (delegates detail to `version-bumper` if needed)
2. Bilingual release notes file creation
3. 7-location consistency verification
4. Conventional Commits message drafting
5. `git add -A && git commit` execution
It **stops before** `git push` or `gh pr create`. Use the `pr-submitter` skill for those steps.
### Temporary File Convention
Any temporary files created during release prep (e.g., draft changelogs) must:
- Be written to the project's `.temp/` directory, **NOT** system `/tmp`
- Be cleaned up before commit using `rm -f .temp/file_name`
- Never be committed to git (add `.temp/` to `.gitignore`)
---
## Workflow
### Step 1 — Collect Release Info
Ask the user (or infer from current state) the following:
- **Plugin name** and **type** (actions / filters / pipes / tools)
- **New version number** (e.g., `0.8.0`)
- **Key changes** in English and Chinese (1-5 bullet points each)
If a `What's New` section already exists in README.md, extract it as the source of truth.
### Step 2 — Sync Version Across 7 Locations
Verify AND update the version string in all of the following. Mark each as ✅ or ❌:
| # | File | Location |
|---|------|----------|
| 1 | `plugins/{type}/{name}/{name}.py` | `version:` in docstring |
| 2 | `plugins/{type}/{name}/README.md` | `**Version:** x.x.x` metadata line |
| 3 | `plugins/{type}/{name}/README_CN.md` | `**Version:** x.x.x` metadata line |
| 4 | `docs/plugins/{type}/{name}.md` | `**Version:** x.x.x` metadata line |
| 5 | `docs/plugins/{type}/{name}.zh.md` | `**Version:** x.x.x` metadata line |
| 6 | `docs/plugins/{type}/index.md` | version badge for this plugin |
| 7 | `docs/plugins/{type}/index.zh.md` | version badge for this plugin |
Additionally update the root-level **updated date badge** in:
- `README.md``![updated](https://img.shields.io/badge/YYYY--MM--DD-gray?style=flat)`
- `README_CN.md` — same badge format
Use today's date (`YYYY-MM-DD`) for the badge.
### Step 3 — Update What's New (All 4 Doc Files)
The `What's New` / `最新更新` section must contain **only the most recent release's changes**. Previous entries should be removed from this section (they live in CHANGELOG or release notes files).
Update these 4 files' `What's New` / `最新更新` block consistently:
- `plugins/{type}/{name}/README.md`
- `plugins/{type}/{name}/README_CN.md`
- `docs/plugins/{type}/{name}.md`
- `docs/plugins/{type}/{name}.zh.md`
### Step 4 — Create Bilingual Release Notes Files
Create two versioned release notes files:
**Path**: `plugins/{type}/{name}/v{version}.md`
**Path**: `plugins/{type}/{name}/v{version}_CN.md`
#### Required Sections
Each file must include:
1. **Title**: `# v{version} Release Notes` (EN) / `# v{version} 版本发布说明` (CN)
2. **Overview**: One paragraph summarizing this release
3. **New Features** / **新功能**: Bulleted list of features
4. **Bug Fixes** / **问题修复**: Bulleted list of fixes
5. **Migration Notes** / **迁移说明**: Breaking changes or Valve key renames (omit section if none)
6. **Companion Plugins** / **配套插件** (optional): If a companion plugin was updated
If a release notes file already exists for this version, update it rather than creating a new one.
#### Full Coverage Rule (Mandatory)
Release notes must cover **all updates in the current release scope** and not only headline features.
Minimum required coverage in both EN/CN files:
- New features and capability enhancements
- Bug fixes and reliability fixes
- Documentation/README/doc-mirror updates that affect user understanding or usage
- Terminology/i18n/wording fixes that change visible behavior or messaging
Before commit, cross-check release notes against `git diff` and ensure no meaningful update is omitted.
### Step 5 — Verify Consistency (Pre-Commit Check)
Run the consistency check script:
```bash
python3 scripts/check_version_consistency.py
```
If issues are found, fix them before proceeding. Do not commit with inconsistencies.
### Step 6 — Draft Conventional Commits Message
Generate the commit message following `commit-message.instructions.md` rules:
- **Language**: English ONLY
- **Format**: `type(scope): subject` + blank line + body bullets
- **Scope**: use plugin folder name (e.g., `github-copilot-sdk`)
- **Body**: 1-3 bullets summarizing key changes
- Explicitly mention "READMEs and docs synced" if version was bumped
Present the full commit message to the user for review before executing.
### Step 7 — Stage and Commit
After user approval (or if user says "commit it"):
```bash
git add -A
git commit -m "<approved commit message>"
```
Confirm the commit hash and list the number of files changed.
---
## Checklist (Auto-Verify Before Commit)
- [ ] `version:` in `.py` docstring matches target version
- [ ] `**Version:**` in all 4 README/docs files matches
- [ ] Both `index.md` version badges updated
- [ ] Root `README.md` and `README_CN.md` date badges updated to today
- [ ] `What's New` / `最新更新` contains ONLY the latest release
- [ ] Release notes include all meaningful updates from the current diff (feature + fix + docs/i18n)
- [ ] `v{version}.md` and `v{version}_CN.md` created or updated
- [ ] `python3 scripts/check_version_consistency.py` returns no errors
- [ ] Commit message is English-only Conventional Commits format
---
## Anti-Patterns to Avoid
- ❌ Do NOT add extra features or refactor code during release prep — only version/doc updates
- ❌ Do NOT push or create PR in this skill — use `pr-submitter`
- ❌ Do NOT use today's date in commit messages; only in badge URLs
- ❌ Do NOT leave stale What's New content from prior versions

View File

@@ -0,0 +1,31 @@
---
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)
- **Open Terminal**: `../open-terminal/` (Terminal integration service)
### 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.

View File

@@ -18,7 +18,7 @@ This is a **universal testing framework** for publishing the latest `github_copi
| Attribute | Fixed Value |
|------|--------|
| **Deployment Script** | `/Users/fujie/app/python/oui/openwebui-extensions/scripts/update_pipe.py` |
| **Deployment Script** | `/Users/fujie/app/python/oui/openwebui-extensions/scripts/deploy_pipe.py` |
| **Python Path** | `/opt/homebrew/Caskroom/miniconda/base/envs/ai/bin/python3` |
| **Test URL** | `http://localhost:3003/?model=github_copilot_official_sdk_pipe.github_copilot_sdk-gpt-4.1` |
@@ -36,11 +36,11 @@ Example: *Modified tool calling logic -> Test prompt should trigger a specific t
Use the `run_command` tool to execute the fixed update task:
```bash
/opt/homebrew/Caskroom/miniconda/base/envs/ai/bin/python3 /Users/fujie/app/python/oui/openwebui-extensions/scripts/update_pipe.py
/opt/homebrew/Caskroom/miniconda/base/envs/ai/bin/python3 /Users/fujie/app/python/oui/openwebui-extensions/scripts/deploy_pipe.py
```
> **Mechanism**: `update_pipe.py` automatically loads the API Key from `scripts/.env` in the same directory.
> **Verification**: Look for `✅ Successfully updated... version X.X.X`. If a 401 error occurs, remind the user to generate a new API Key in OpenWebUI and update `.env`.
> **Mechanism**: `deploy_pipe.py` automatically loads the API Key from `scripts/.env` in the same directory.
> **Verification**: Look for `✅ Successfully updated... version X.X.X` or `✅ Successfully created...`. If a 401 error occurs, remind the user to generate a new API Key in OpenWebUI and update `.env`.
### Step 3: Verify via Browser Subagent (Verify)

View 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>"
```

View 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])

View File

@@ -8,7 +8,43 @@ description: Automatically synchronizes plugin READMEs to the official documenta
## Overview
Automates the mirroring of `plugins/{type}/{name}/README.md` to `docs/plugins/{type}/{name}.md`.
## Docs-Only Mode (No Release Changes)
Use this mode when the request is "only sync docs".
- Only update documentation mirror files under `docs/plugins/**`.
- Do **not** bump plugin version.
- Do **not** modify plugin code (`plugins/**.py`) unless explicitly requested.
- Do **not** update root badges/dates for release.
- Do **not** run release preparation steps.
## Workflow
1. Identify changed READMEs.
2. Copy content to corresponding mirror paths.
3. Update version badges in `docs/plugins/{type}/index.md`.
## Commands
### Sync all mirrors (EN + ZH)
```bash
python .github/skills/doc-mirror-sync/scripts/sync.py
```
### Sync only one plugin (EN only)
```bash
cp plugins/<type>/<name>/README.md docs/plugins/<type>/<name>.md
```
### Sync only one plugin (EN + ZH)
```bash
cp plugins/<type>/<name>/README.md docs/plugins/<type>/<name>.md
cp plugins/<type>/<name>/README_CN.md docs/plugins/<type>/<name>.zh.md
```
## Notes
- If asked for English-only update, sync only `README.md` -> `.md` mirror.
- If both languages are requested, sync both `README.md` and `README_CN.md`.
- After syncing, verify git diff only contains docs file changes.

165
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,165 @@
name: 🐛 Bug Report
description: Report a bug or issue with OpenWebUI plugins
title: "[BUG] "
labels: ["bug"]
assignees: []
body:
- type: markdown
attributes:
value: |
Thanks for reporting a bug! Please provide clear information to help us reproduce and fix the issue.
- type: dropdown
id: plugin-type
attributes:
label: Plugin Type
description: Which type of plugin is affected?
options:
- Action
- Filter
- Pipe
- Pipeline
- Tool
- Other
validations:
required: true
- type: dropdown
id: plugin-name
attributes:
label: Plugin Name
description: Which plugin has the issue?
options:
- "Select a plugin..."
- "Action - Deep Dive"
- "Action - Export to Word Enhanced"
- "Action - Export to Excel"
- "Action - Flash Card"
- "Action - Smart Infographic"
- "Action - Smart Mind Map"
- "Filter - Async Context Compression"
- "Filter - Context & Model Enhancement Filter"
- "Filter - Folder Memory"
- "Filter - GitHub Copilot SDK Files Filter"
- "Filter - Markdown Normalizer"
- "Filter - Gemini Multimodel Filter"
- "Pipeline - MOE Prompt Refiner"
- "Pipe - GitHub Copilot SDK"
- "Pipe - iFlow SDK"
- "Tool - OpenWebUI Skills Manager"
- "Tool - Smart Infographic Tool"
- "Tool - Smart Mind Map Tool"
- "Other / Not Listed"
validations:
required: true
- type: input
id: plugin-name-other
attributes:
label: Plugin Name (if not in list)
description: If you selected 'Other / Not Listed', please specify the plugin name
placeholder: "Plugin name"
validations:
required: false
- type: textarea
id: description
attributes:
label: Description
description: Clearly describe the bug. What went wrong? What did you expect?
placeholder: |
I tried to use [feature], but instead of [expected behavior], it [actual behavior].
Error message (if any):
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to Reproduce
description: How can we reproduce this issue?
placeholder: |
1. Click on...
2. Enter...
3. See error...
validations:
required: true
- type: input
id: openwebui-version
attributes:
label: OpenWebUI Version
description: What version of OpenWebUI are you using?
placeholder: "e.g., 0.3.0 or main"
validations:
required: true
- type: textarea
id: environment
attributes:
label: Operating System & Container
description: "Your operating system and deployment method"
placeholder: |
Example 1:
OS: macOS 14.3
Container: Docker (version 24.0.x)
Example 2:
OS: Ubuntu 22.04 LTS
Deployment: Docker Compose
validations:
required: true
- type: markdown
attributes:
value: |
## 📋 Debug Information (Optional)
To help diagnose the issue faster, please provide relevant logs:
**Frontend Console Logs** (Recommended):
1. Enable: Click avatar → Settings → General → Enable Plugin Debug Output
2. Open DevTools: Press `F12` (or `Cmd+Option+I` on Mac)
3. Go to Console tab and copy any error messages or `🛠️ Debug` output
**Server-Side Logs**:
- Docker: `docker logs <container-id>`
- Local: Check terminal output or log files
- type: textarea
id: logs
attributes:
label: Error Logs (Optional)
description: "Paste frontend console or server logs that show the error"
placeholder: |
Error message from console:
[Your logs here]
render: bash
validations:
required: false
- type: textarea
id: additional
attributes:
label: Additional Information (Optional)
description: "Screenshots, related issues, or other helpful details"
placeholder: |
- Screenshots (if applicable)
- Related issues or discussions
- Steps you've already tried to fix it
validations:
required: false
- type: checkboxes
id: checklist
attributes:
label: Checklist
options:
- label: I have searched for duplicate issues
required: true
- label: I have provided clear reproduction steps
required: true
- label: I have mentioned OpenWebUI version and OS/container info
required: true

11
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Documentation
url: https://docs.openwebui.com/
about: Official OpenWebUI documentation
- name: Discussions
url: https://github.com/Fu-Jie/openwebui-extensions/discussions
about: Ask questions and discuss with the community
- name: OpenWebUI Repository
url: https://github.com/open-webui/open-webui
about: Main OpenWebUI project

View File

@@ -0,0 +1,104 @@
name: ✨ Feature Request
description: Suggest a new feature or improvement
title: "[FEATURE] "
labels: ["enhancement"]
assignees: []
body:
- type: markdown
attributes:
value: |
Thanks for your suggestion! Please describe the feature you'd like to see.
- type: dropdown
id: plugin-type
attributes:
label: Plugin Type (Optional)
description: Is this for a specific plugin type?
options:
- Action
- Filter
- Pipe
- Pipeline
- Tool
- Core/General
- Documentation
- Other
validations:
required: false
- type: dropdown
id: plugin-name
attributes:
label: Plugin Name (Optional)
description: Which plugin would benefit from this feature?
options:
- "Select a plugin..."
- "Action - Deep Dive"
- "Action - Export to Word Enhanced"
- "Action - Export to Excel"
- "Action - Flash Card"
- "Action - Smart Infographic"
- "Action - Smart Mind Map"
- "Filter - Async Context Compression"
- "Filter - Context & Model Enhancement Filter"
- "Filter - Folder Memory"
- "Filter - GitHub Copilot SDK Files Filter"
- "Filter - Markdown Normalizer"
- "Filter - Gemini Multimodel Filter"
- "Pipeline - MOE Prompt Refiner"
- "Pipe - GitHub Copilot SDK"
- "Pipe - iFlow SDK"
- "Tool - OpenWebUI Skills Manager"
- "Tool - Smart Infographic Tool"
- "Tool - Smart Mind Map Tool"
- "Core/General Improvement"
- "Other / Not Listed"
validations:
required: false
- type: textarea
id: description
attributes:
label: Feature Description
description: Clearly describe the feature you're requesting
placeholder: |
What feature would you like to see?
How would it work?
validations:
required: true
- type: textarea
id: motivation
attributes:
label: Motivation & Use Case
description: Why is this feature important? What problem does it solve?
placeholder: |
What's the pain point this solves?
How would it improve your workflow?
Who else would benefit from this?
validations:
required: true
- type: textarea
id: additional
attributes:
label: Additional Information (Optional)
description: "Mockups, alternatives considered, references, or examples"
placeholder: |
- Links to related plugins or tools
- Screenshots or mockups
- Alternative approaches you've considered
- Code examples or references
validations:
required: false
- type: checkboxes
id: checklist
attributes:
label: Checklist
options:
- label: I have searched existing issues/discussions for similar requests
required: true
- label: This feature would be useful for multiple users
required: false

View File

@@ -8,7 +8,43 @@ description: Automatically synchronizes plugin READMEs to the official documenta
## Overview
Automates the mirroring of `plugins/{type}/{name}/README.md` to `docs/plugins/{type}/{name}.md`.
## Docs-Only Mode (No Release Changes)
Use this mode when the request is "only sync docs".
- Only update documentation mirror files under `docs/plugins/**`.
- Do **not** bump plugin version.
- Do **not** modify plugin code (`plugins/**.py`) unless explicitly requested.
- Do **not** update root badges/dates for release.
- Do **not** run release preparation steps.
## Workflow
1. Identify changed READMEs.
2. Copy content to corresponding mirror paths.
3. Update version badges in `docs/plugins/{type}/index.md`.
## Commands
### Sync all mirrors (EN + ZH)
```bash
python .github/skills/doc-mirror-sync/scripts/sync.py
```
### Sync only one plugin (EN only)
```bash
cp plugins/<type>/<name>/README.md docs/plugins/<type>/<name>.md
```
### Sync only one plugin (EN + ZH)
```bash
cp plugins/<type>/<name>/README.md docs/plugins/<type>/<name>.md
cp plugins/<type>/<name>/README_CN.md docs/plugins/<type>/<name>.zh.md
```
## Notes
- If asked for English-only update, sync only `README.md` -> `.md` mirror.
- If both languages are requested, sync both `README.md` and `README_CN.md`.
- After syncing, verify git diff only contains docs file changes.

View File

@@ -89,6 +89,18 @@ Each file must include:
If a release notes file already exists for this version, update it rather than creating a new one.
#### Full Coverage Rule (Mandatory)
Release notes must cover **all updates in the current release scope** and not only headline features.
Minimum required coverage in both EN/CN files:
- New features and capability enhancements
- Bug fixes and reliability fixes
- Documentation/README/doc-mirror updates that affect user understanding or usage
- Terminology/i18n/wording fixes that change visible behavior or messaging
Before commit, cross-check release notes against `git diff` and ensure no meaningful update is omitted.
### Step 5 — Verify Consistency (Pre-Commit Check)
Run the consistency check script:
@@ -130,6 +142,7 @@ Confirm the commit hash and list the number of files changed.
- [ ] Both `index.md` version badges updated
- [ ] Root `README.md` and `README_CN.md` date badges updated to today
- [ ] `What's New` / `最新更新` contains ONLY the latest release
- [ ] Release notes include all meaningful updates from the current diff (feature + fix + docs/i18n)
- [ ] `v{version}.md` and `v{version}_CN.md` created or updated
- [ ] `python3 scripts/check_version_consistency.py` returns no errors
- [ ] Commit message is English-only Conventional Commits format

View File

@@ -17,6 +17,7 @@ When assisting with the development of `openwebui-extensions`, you (Antigravity)
- **Open WebUI**: `../open-webui/` (Core platform context)
- **Skills**: `../skills/` (Reusable expertise library)
- **Awesome Copilot**: `../awesome-copilot/` (Shared extensions & resources)
- **Open Terminal**: `../open-terminal/` (Terminal integration service)
### Plugin-Specific (Relevant to GitHub Copilot SDK)

View File

@@ -22,6 +22,11 @@ on:
- main
paths:
- 'plugins/**/*.py'
- 'plugins/**/README.md'
- 'plugins/**/README_CN.md'
- 'plugins/**/v*.md'
- 'plugins/**/v*_CN.md'
- 'docs/plugins/**/*.md'
tags:
- 'v*'
@@ -52,6 +57,8 @@ permissions:
jobs:
check-changes:
runs-on: ubuntu-latest
# Skip release if commit message contains [skip release]
if: ${{ !contains(github.event.head_commit.message, '[skip release]') }}
env:
LANG: en_US.UTF-8
LC_ALL: en_US.UTF-8
@@ -59,6 +66,8 @@ jobs:
has_changes: ${{ steps.detect.outputs.has_changes }}
changed_plugins: ${{ steps.detect.outputs.changed_plugins }}
release_notes: ${{ steps.detect.outputs.release_notes }}
has_doc_changes: ${{ steps.detect.outputs.has_doc_changes }}
changed_doc_files: ${{ steps.detect.outputs.changed_doc_files }}
steps:
- name: Checkout repository
@@ -95,17 +104,19 @@ jobs:
python scripts/extract_plugin_versions.py --json --output current_versions.json
# Get previous plugin versions by checking out old plugins
if git worktree add /tmp/old_repo ${COMPARE_REF} 2>/dev/null; then
if [ -d /tmp/old_repo/plugins ]; then
python scripts/extract_plugin_versions.py --plugins-dir /tmp/old_repo/plugins --json --output old_versions.json
OLD_WORKTREE=$(mktemp -d)
if git worktree add "$OLD_WORKTREE" ${COMPARE_REF} 2>/dev/null; then
if [ -d "$OLD_WORKTREE/plugins" ]; then
python scripts/extract_plugin_versions.py --plugins-dir "$OLD_WORKTREE/plugins" --json --output old_versions.json
else
echo "[]" > old_versions.json
fi
git worktree remove /tmp/old_repo 2>/dev/null || true
git worktree remove "$OLD_WORKTREE" 2>/dev/null || true
else
echo "Failed to create worktree, using empty version list"
echo "[]" > old_versions.json
fi
rm -rf "$OLD_WORKTREE" 2>/dev/null || true
# Compare versions and generate release notes
python scripts/extract_plugin_versions.py --compare old_versions.json --ignore-removed --output changes.md
@@ -113,9 +124,29 @@ jobs:
echo "=== Version Changes ==="
cat changes.md
# Detect documentation/release-note changes that should be reflected in release notes
git diff --name-only "$COMPARE_REF"..HEAD -- \
'plugins/**/README.md' \
'plugins/**/README_CN.md' \
'plugins/**/v*.md' \
'plugins/**/v*_CN.md' \
'docs/plugins/**/*.md' > changed_docs.txt || true
if [ -s changed_docs.txt ]; then
echo "has_doc_changes=true" >> $GITHUB_OUTPUT
echo "changed_doc_files<<EOF" >> $GITHUB_OUTPUT
cat changed_docs.txt >> $GITHUB_OUTPUT
echo "" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
else
echo "has_doc_changes=false" >> $GITHUB_OUTPUT
echo "changed_doc_files=" >> $GITHUB_OUTPUT
fi
# Check if there are any changes
if grep -q "No changes detected" changes.md; then
# Only trigger release if there are actual version changes, not just doc changes
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "changed_plugins=" >> $GITHUB_OUTPUT
else
@@ -214,7 +245,6 @@ jobs:
id: plugins
run: |
python scripts/extract_plugin_versions.py --json --output plugin_versions.json
python scripts/extract_plugin_versions.py --json --output plugin_versions.json
- name: Collect plugin files for release
id: collect_files
@@ -308,9 +338,9 @@ jobs:
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -n "$LAST_TAG" ]; then
COMMITS=$(git log ${LAST_TAG}..HEAD --pretty=format:"- %s" --no-merges -- plugins/ | head -20)
COMMITS=$(git log ${LAST_TAG}..HEAD --pretty=format:"- **%s**%n%b" --no-merges -- plugins/ | sed '/^$/d' | head -40)
else
COMMITS=$(git log --pretty=format:"- %s" --no-merges -10 -- plugins/)
COMMITS=$(git log --pretty=format:"- **%s**%n%b" --no-merges -10 -- plugins/ | sed '/^$/d')
fi
{
@@ -328,9 +358,30 @@ jobs:
NOTES: ${{ github.event.inputs.release_notes }}
DETECTED_CHANGES: ${{ needs.check-changes.outputs.release_notes }}
COMMITS: ${{ steps.commits.outputs.commits }}
DOC_FILES: ${{ needs.check-changes.outputs.changed_doc_files }}
run: |
> release_notes.md
# 1. Release notes from v*.md files (highest priority, shown first)
if [ -n "$DOC_FILES" ]; then
RELEASE_NOTE_FILES=$(echo "$DOC_FILES" | grep -E '^plugins/.*/v[^/]*\.md$' | grep -v '_CN\.md$' || true)
if [ -n "$RELEASE_NOTE_FILES" ]; then
while IFS= read -r file; do
[ -z "$file" ] && continue
if [ -f "$file" ]; then
# Inject plugin README link before each release note file content
plugin_dir=$(dirname "$file")
readme_url="https://github.com/Fu-Jie/openwebui-extensions/blob/main/${plugin_dir}/README.md"
echo "> 📖 [Plugin README](${readme_url})" >> release_notes.md
echo "" >> release_notes.md
cat "$file" >> release_notes.md
echo "" >> release_notes.md
fi
done <<< "$RELEASE_NOTE_FILES"
fi
fi
# 2. Plugin version changes detected by script
if [ -n "$TITLE" ]; then
echo "## $TITLE" >> release_notes.md
echo "" >> release_notes.md
@@ -343,6 +394,7 @@ jobs:
echo "" >> release_notes.md
fi
# 3. Commits (Conventional Commits format with body)
if [ -n "$COMMITS" ]; then
echo "## Commits" >> release_notes.md
echo "" >> release_notes.md
@@ -357,8 +409,6 @@ jobs:
echo "" >> release_notes.md
fi
cat >> release_notes.md << 'EOF'
## Download
@@ -389,6 +439,7 @@ jobs:
cat release_notes.md
- name: Create Git Tag
if: ${{ !startsWith(github.ref, 'refs/tags/v') }}
run: |
VERSION="${{ steps.version.outputs.version }}"

View File

@@ -5,7 +5,7 @@
English | [中文](./README_CN.md)
A collection of enhancements, plugins, and prompts for [OpenWebUI](https://github.com/open-webui/open-webui), developed and curated for personal use to extend functionality and improve experience.
A collection of enhancements, plugins, and prompts for [open-webui](https://github.com/open-webui/open-webui), developed and curated for personal use to extend functionality and improve experience.
<!-- STATS_START -->
## 📊 Community Stats
@@ -24,12 +24,12 @@ A collection of enhancements, plugins, and prompts for [OpenWebUI](https://githu
| Rank | Plugin | Version | Downloads | Views | 📅 Updated |
| :---: | :--- | :---: | :---: | :---: | :---: |
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | ![p1_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_version.json&style=flat) | ![p1_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_dl.json&style=flat) | ![p1_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--27-gray?style=flat) |
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | ![p1_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_version.json&style=flat) | ![p1_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_dl.json&style=flat) | ![p1_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--04-gray?style=flat) |
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | ![p2_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_version.json&style=flat) | ![p2_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_dl.json&style=flat) | ![p2_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--13-gray?style=flat) |
| 🥉 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | ![p3_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_version.json&style=flat) | ![p3_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_dl.json&style=flat) | ![p3_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--27-gray?style=flat) |
| 🥉 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | ![p3_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_version.json&style=flat) | ![p3_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_dl.json&style=flat) | ![p3_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--28-gray?style=flat) |
| 4⃣ | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | ![p4_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_version.json&style=flat) | ![p4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_dl.json&style=flat) | ![p4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--13-gray?style=flat) |
| 5⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | ![p5_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_version.json&style=flat) | ![p5_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_dl.json&style=flat) | ![p5_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--21-gray?style=flat) |
| 6⃣ | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | ![p6_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_version.json&style=flat) | ![p6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_dl.json&style=flat) | ![p6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--13-gray?style=flat) |
| 5⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | ![p5_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_version.json&style=flat) | ![p5_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_dl.json&style=flat) | ![p5_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--28-gray?style=flat) |
| 6⃣ | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | ![v](https://img.shields.io/badge/v-0.3.7-blue?style=flat) | ![p6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_dl.json&style=flat) | ![p6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--13-gray?style=flat) |
### 📈 Total Downloads Trend
@@ -40,20 +40,27 @@ A collection of enhancements, plugins, and prompts for [OpenWebUI](https://githu
## 🌟 Star Features
### 1. [GitHub Copilot SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) [![Market](https://img.shields.io/badge/Get-Market-blue?style=flat-square&logo=openwebui)](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4)
### 1. [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) ![v0.9.1](https://img.shields.io/badge/v0.9.1-blue?style=flat-square) ![active-dev](https://img.shields.io/badge/active--dev-orange?style=flat-square) ![downloads](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_dl.json&style=flat-square) ![views](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_vw.json&style=flat-square)
**The ultimate autonomous Agent for OpenWebUI.** Transforming your LLM into a powerful OS-level engineer with native code execution, deep tool autonomy, and professional skill management.
**The ultimate autonomous Agent integration for OpenWebUI.** Deeply bridging GitHub Copilot SDK with your OpenWebUI ecosystem. It enables the Agent to autonomously perform **intent recognition**, **web search**, and **context compaction** while reusing your existing tools, skills, and configurations for a professional, full-featured experience.
> [!TIP]
> **No GitHub Copilot subscription required!** Supports **BYOK (Bring Your Own Key)** mode using your own OpenAI/Anthropic API keys.
#### 🚀 Key Leap (v0.9.0+)
#### 🚀 Key Leap (v0.9.1+)
- **🧩 Skills Revolution**: Native support for **SKILL directories** (scripts, templates, resources) coupled with a **Bidirectional Bridge** to OpenWebUI Workspace Skills.
- **🔌 Seamless Ecosystem Integration**: Automatically injects and reuses your OpenWebUI **Tools**, **MCP**, **OpenAPI Servers**, and **Skills**, significantly enhancing the Agent's capabilities through your existing setup.
- **🌐 Language Consistency**: System prompts mandate that Agent output language remains strictly consistent with user input.
- **🧩 Skills Revolution**: Native support for **SKILL directories** and a **Bidirectional Bridge** to OpenWebUI Workspace Skills.
- **🛡️ Secure Isolation**: Strict user/session-level **Workspace Sandboxing** with persistent configuration.
- **📊 Interactive Delivery**: Professional **File Delivery Protocol** for instant HTML artifacts and persistent downloadable results.
- **📊 Interactive Delivery**: Full support for **HTML Artifacts** and **RichUI** rendering, providing instant interactive previews and persistent downloadable results.
- **🛠️ Deterministic Toolchain**: Built-in specialized tools for skill lifecycles (`manage_skills`) and system optimization.
> [!TIP]
> **💡 Pro Tip: Enhanced Visualization**
> We highly recommend asking the Agent to install the [Visual Explainer](https://github.com/nicobailon/visual-explainer) skill during your conversation. It dramatically improves the aesthetics and interactivity of generated **HTML Artifacts**. Simply tell the AI:
> "Please install this skill: <https://github.com/nicobailon/visual-explainer>" to get started.
#### 📺 Demo: Visual Skills & Data Analysis
![GitHub Copilot SDK Skill Demo](https://github.com/Fu-Jie/openwebui-extensions/raw/main/docs/assets/videos/skill.gif)
@@ -64,19 +71,19 @@ A collection of enhancements, plugins, and prompts for [OpenWebUI](https://githu
- **[GitHub Star Forecasting](./docs/plugins/pipes/star-prediction-example.md)**: Automatically parsing CSV data, writing analysis scripts, and generating interactive growth dashboards.
- **[Video Optimization](./docs/plugins/pipes/video-processing-example.md)**: Direct control of system-level tools (FFmpeg) to accelerate and compress media with professional color optimization.
### 2. [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) [![Market](https://img.shields.io/badge/Get-Market-blue?style=flat-square&logo=openwebui)](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a)
### 2. [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a)
**Experience interactive thinking.** Seamlessly transforms complex chat sessions into structured, clickable mind maps for better visual modeling and rapid idea extraction.
### 3. [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) [![Market](https://img.shields.io/badge/Get-Market-blue?style=flat-square&logo=openwebui)](https://openwebui.com/posts/smart_infographic_ad6f0c7f)
### 3. [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f)
**Professional data storytelling.** Converts raw information into sleek, boardroom-ready infographics powered by AntV, perfect for summarizing long-form content instantly.
### 4. [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) [![Market](https://img.shields.io/badge/Get-Market-blue?style=flat-square&logo=openwebui)](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315)
### 4. [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315)
**High-fidelity reporting.** Export conversation history into professionally formatted Word documents with preserved headers, code blocks, and math formulas.
### 5. [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) [![Market](https://img.shields.io/badge/Get-Market-blue?style=flat-square&logo=openwebui)](https://openwebui.com/posts/async_context_compression_b1655bc8)
### 5. [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8)
**Maximize your context window.** Intelligently compresses chat history using LLM logic to save tokens and costs while maintaining a high-quality reasoning chain.
@@ -97,6 +104,11 @@ Located in the `plugins/` directory, containing Python-based enhancements:
- **Export to Excel** (`export_to_excel`): Exports chat history to Excel files.
- **Export to Word** (`export_to_docx`): Exports chat history to Word documents.
### Tools
- **Smart Mind Map Tool** (`smart-mind-map-tool`): The tool version of Smart Mind Map, enabling AI proactive/autonomous invocation.
- **OpenWebUI Skills Manager Tool** (`openwebui-skills-manager-tool`): Native tool for managing OpenWebUI skills.
### Filters
- **GitHub Copilot SDK Files Filter** (`github_copilot_sdk_files_filter`): Essential companion for Copilot SDK. Bypasses RAG to ensure full file accessibility for Agents.

View File

@@ -21,12 +21,13 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
| 排名 | 插件 | 版本 | 下载 | 浏览 | 📅 更新 |
| :---: | :--- | :---: | :---: | :---: | :---: |
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | ![p1_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_version.json&style=flat) | ![p1_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_dl.json&style=flat) | ![p1_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--27-gray?style=flat) |
| 🥇 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | ![p1_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_version.json&style=flat) | ![p1_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_dl.json&style=flat) | ![p1_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p1_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--03--04-gray?style=flat) |
| 🥈 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | ![p2_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_version.json&style=flat) | ![p2_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_dl.json&style=flat) | ![p2_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p2_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--13-gray?style=flat) |
| 🥉 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | ![p3_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_version.json&style=flat) | ![p3_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_dl.json&style=flat) | ![p3_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--27-gray?style=flat) |
| 🆕 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | ![p0_version](https://img.shields.io/badge/版本-0.9.1-blue?style=flat) | ![p0_dl](https://img.shields.io/badge/下载-热门-red?style=flat) | ![p0_vw](https://img.shields.io/badge/浏览-最新-green?style=flat) | ![updated](https://img.shields.io/badge/2026--03--04-gray?style=flat) |
| 🥉 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | ![p3_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_version.json&style=flat) | ![p3_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_dl.json&style=flat) | ![p3_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p3_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--28-gray?style=flat) |
| 4⃣ | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | ![p4_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_version.json&style=flat) | ![p4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_dl.json&style=flat) | ![p4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p4_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--13-gray?style=flat) |
| 5⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | ![p5_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_version.json&style=flat) | ![p5_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_dl.json&style=flat) | ![p5_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--21-gray?style=flat) |
| 6⃣ | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | ![p6_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_version.json&style=flat) | ![p6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_dl.json&style=flat) | ![p6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--13-gray?style=flat) |
| 5⃣ | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | ![p5_version](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_version.json&style=flat) | ![p5_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_dl.json&style=flat) | ![p5_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p5_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--28-gray?style=flat) |
| 6⃣ | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | ![v](https://img.shields.io/badge/v-0.3.7-blue?style=flat) | ![p6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_dl.json&style=flat) | ![p6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_p6_vw.json&style=flat) | ![updated](https://img.shields.io/badge/2026--02--13-gray?style=flat) |
### 📈 总下载量累计趋势
@@ -37,19 +38,26 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
## 🌟 精选功能
### 1. [GitHub Copilot SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) [![Market](https://img.shields.io/badge/Get-Market-blue?style=flat-square&logo=openwebui)](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4)
### 1. [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) [![Market](https://img.shields.io/badge/Get-Market-blue?style=flat-square&logo=openwebui)](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) ![积极开发中](https://img.shields.io/badge/状态-积极开发中-orange?style=flat-square)
**OpenWebUI 终极自主 Agent 增强**LLM 转化为具备 OS 级操作能力的专业工程师,支持原生代码执行、深度工具自治以及专业技能管理
**OpenWebUI 终极自主 Agent 深度集成**GitHub Copilot SDK 与 OpenWebUI 生态完美桥接。它允许 Agent 具备**智能意图识别**、**自主网页搜索**与**自动上下文压缩**能力,同时直接复用您现有的工具、技能与配置,通过全功能 Skill 体系带来极致的专业交互体验
> [!TIP]
> **无需 GitHub Copilot 订阅!** 支持 **BYOK (Bring Your Own Key)** 模式,使用你自己的 OpenAI/Anthropic API Key。
#### 🚀 核心进化 (v0.9.0+)
#### 🚀 核心进化 (v0.9.1+)
- **🧩 技能革命**: 原生支持 **SKILL 目录**(含脚本、模板与资源),并实现与 OpenWebUI **工作区 > Skills** 的深度双向桥接
- **🔌 生态深度注入**: 自动读取并复用 OpenWebUI **工具 (Tools)**、**MCP**、**OpenAPI Server** 与 **技能 (Skills)**,显著增强 Agent 的实战能力
- **🧩 技能革命**: 原生支持 **SKILL 目录**,并实现与 OpenWebUI **工作区 > Skills** 的深度双向桥接。
- **🛡️ 安全沙箱**: 严格的用户/会话级 **工作区隔离** 与持久化配置环境。
- **📊 交互交付**: 专业 **文件交付协议**,支持即时预览交互式 HTML Artifacts 与持久化结果下载。
- **📊 交互交付**: 完整支持 **HTML Artifacts****RichUI** 渲染,提供即时预览交互式应用程序与持久化结果下载。
- **🛠️ 确定性工具链**: 内置 `manage_skills` 等专业工具,赋予 Agent 完整的技能生命周期管理能力。
- **🌐 语言一致性**: 提示词强制要求 Agent 输出语言与用户输入保持一致,确保国际化体验。
> [!TIP]
> **💡 进阶实战建议**
> 强烈推荐在对话中让 Agent 为其安装 [Visual Explainer](https://github.com/nicobailon/visual-explainer) 技能。该技能能显著提升 **HTML Artifacts** 的美观度与交互深度,只需对 AI 说:
> “请帮我安装这个技能:<https://github.com/nicobailon/visual-explainer”> 即可瞬间启用。
#### 📺 演示:可视化技能与数据分析
@@ -94,6 +102,11 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
- **Export to Excel** (`export_to_excel`): 将对话内容导出为 Excel 文件。
- **Export to Word** (`export_to_docx`): 将对话内容导出为 Word 文档。
### Tools (工具)
- **智能思维导图工具** (`smart-mind-map-tool`): 思维导图的 Tool 版本,支持 AI 主动/自主调用。
- **OpenWebUI Skills 管理工具** (`openwebui-skills-manager-tool`): 用于管理 OpenWebUI Skills 的原生工具。
### Filters (消息处理)
- **GitHub Copilot SDK Files Filter** (`github_copilot_sdk_files_filter`): Copilot SDK 必备搭档。绕过 RAG确保 Agent 能真正看到你的每一个文件。
@@ -105,7 +118,7 @@ OpenWebUI 增强功能集合。包含个人开发与收集的插件、提示词
### Pipes (模型管道)
- **GitHub Copilot SDK** (`github-copilot-sdk`): GitHub Copilot SDK 官方集成。支持动态模型、多轮对话、流式输出、图片输入及无限会话
- **GitHub Copilot SDK** (`github-copilot-sdk`): 深度集成 GitHub Copilot SDK 的强大 Agent。支持智能意图识别、自主网页搜索与上下文压缩并能够无缝复用 OpenWebUI 的工具 (Tools)、MCP 与 OpenAPI Server
### Pipelines (工作流管道)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 MiB

After

Width:  |  Height:  |  Size: 10 MiB

View File

@@ -1,16 +1,17 @@
{
"total_posts": 25,
"total_downloads": 6379,
"total_views": 67827,
"total_upvotes": 254,
"total_downvotes": 3,
"total_saves": 337,
"total_downloads": 7058,
"total_views": 75199,
"total_upvotes": 273,
"total_downvotes": 4,
"total_saves": 372,
"total_comments": 58,
"by_type": {
"post": 6,
"tool": 1,
"post": 5,
"pipe": 1,
"action": 12,
"filter": 4,
"action": 12,
"prompt": 1,
"review": 1
},
@@ -22,13 +23,13 @@
"version": "1.0.0",
"author": "Fu-Jie",
"description": "Intelligently analyzes text content and generates interactive mind maps to help users structure and visualize knowledge.",
"downloads": 1328,
"views": 11410,
"upvotes": 23,
"saves": 59,
"downloads": 1426,
"views": 12082,
"upvotes": 26,
"saves": 63,
"comments": 15,
"created_at": "2025-12-30",
"updated_at": "2026-02-27",
"created_at": "2025-12-31",
"updated_at": "2026-02-28",
"url": "https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a"
},
{
@@ -38,10 +39,10 @@
"version": "1.5.0",
"author": "Fu-Jie",
"description": "AI-powered infographic generator based on AntV Infographic. Supports professional templates, auto-icon matching, and SVG/PNG downloads.",
"downloads": 1076,
"views": 10746,
"downloads": 1155,
"views": 11609,
"upvotes": 25,
"saves": 40,
"saves": 45,
"comments": 10,
"created_at": "2025-12-28",
"updated_at": "2026-02-13",
@@ -54,13 +55,13 @@
"version": "1.2.7",
"author": "Fu-Jie",
"description": "A content normalizer filter that fixes common Markdown formatting issues in LLM outputs, such as broken code blocks, LaTeX formulas, and list formatting. Including LaTeX command protection.",
"downloads": 609,
"views": 6795,
"upvotes": 18,
"saves": 37,
"downloads": 661,
"views": 7239,
"upvotes": 20,
"saves": 40,
"comments": 5,
"created_at": "2026-01-12",
"updated_at": "2026-02-27",
"updated_at": "2026-02-28",
"url": "https://openwebui.com/posts/markdown_normalizer_baaa8732"
},
{
@@ -70,10 +71,10 @@
"version": "0.4.4",
"author": "Fu-Jie",
"description": "Export current conversation from Markdown to Word (.docx) with Mermaid diagrams rendered client-side (Mermaid.js, SVG+PNG), LaTeX math, real hyperlinks, improved tables, syntax highlighting, and blockquote support.",
"downloads": 578,
"views": 4611,
"downloads": 628,
"views": 4995,
"upvotes": 16,
"saves": 30,
"saves": 35,
"comments": 5,
"created_at": "2026-01-03",
"updated_at": "2026-02-13",
@@ -86,13 +87,13 @@
"version": "1.3.0",
"author": "Fu-Jie",
"description": "Reduces token consumption in long conversations while maintaining coherence through intelligent summarization and message compression.",
"downloads": 559,
"views": 5452,
"upvotes": 15,
"saves": 41,
"downloads": 619,
"views": 5875,
"upvotes": 16,
"saves": 46,
"comments": 0,
"created_at": "2025-11-08",
"updated_at": "2026-02-21",
"updated_at": "2026-02-28",
"url": "https://openwebui.com/posts/async_context_compression_b1655bc8"
},
{
@@ -102,10 +103,10 @@
"version": "0.3.7",
"author": "Fu-Jie",
"description": "Extracts tables from chat messages and exports them to Excel (.xlsx) files with smart formatting.",
"downloads": 492,
"views": 2693,
"downloads": 523,
"views": 2898,
"upvotes": 10,
"saves": 8,
"saves": 9,
"comments": 0,
"created_at": "2025-05-30",
"updated_at": "2026-02-13",
@@ -118,8 +119,8 @@
"version": "",
"author": "",
"description": "",
"downloads": 473,
"views": 5498,
"downloads": 523,
"views": 6055,
"upvotes": 9,
"saves": 14,
"comments": 0,
@@ -127,22 +128,6 @@
"updated_at": "2026-01-28",
"url": "https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37"
},
{
"title": "Flash Card",
"slug": "flash_card_65a2ea8f",
"type": "action",
"version": "0.2.4",
"author": "Fu-Jie",
"description": "Quickly generates beautiful flashcards from text, extracting key points and categories.",
"downloads": 285,
"views": 4128,
"upvotes": 13,
"saves": 18,
"comments": 2,
"created_at": "2025-12-30",
"updated_at": "2026-02-13",
"url": "https://openwebui.com/posts/flash_card_65a2ea8f"
},
{
"title": "GitHub Copilot Official SDK Pipe",
"slug": "github_copilot_official_sdk_pipe_ce96f7b4",
@@ -150,15 +135,31 @@
"version": "0.9.0",
"author": "Fu-Jie",
"description": "Integrate GitHub Copilot SDK. Supports dynamic models, multi-turn conversation, streaming, multimodal input, infinite sessions, bidirectional OpenWebUI Skills bridge, and manage_skills tool.",
"downloads": 263,
"views": 4106,
"upvotes": 14,
"downloads": 301,
"views": 4540,
"upvotes": 16,
"saves": 10,
"comments": 6,
"created_at": "2026-01-26",
"updated_at": "2026-02-27",
"updated_at": "2026-02-28",
"url": "https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4"
},
{
"title": "Flash Card",
"slug": "flash_card_65a2ea8f",
"type": "action",
"version": "0.2.4",
"author": "Fu-Jie",
"description": "Quickly generates beautiful flashcards from text, extracting key points and categories.",
"downloads": 295,
"views": 4297,
"upvotes": 13,
"saves": 20,
"comments": 2,
"created_at": "2025-12-30",
"updated_at": "2026-02-13",
"url": "https://openwebui.com/posts/flash_card_65a2ea8f"
},
{
"title": "Deep Dive",
"slug": "deep_dive_c0b846e4",
@@ -166,15 +167,31 @@
"version": "1.0.0",
"author": "Fu-Jie",
"description": "A comprehensive thinking lens that dives deep into any content - from context to logic, insights, and action paths.",
"downloads": 204,
"views": 1631,
"downloads": 211,
"views": 1699,
"upvotes": 6,
"saves": 13,
"saves": 14,
"comments": 0,
"created_at": "2026-01-08",
"updated_at": "2026-01-08",
"url": "https://openwebui.com/posts/deep_dive_c0b846e4"
},
{
"title": "OpenWebUI Skills Manager Tool",
"slug": "openwebui_skills_manager_tool_b4bce8e4",
"type": "tool",
"version": "",
"author": "",
"description": "",
"downloads": 169,
"views": 2629,
"upvotes": 6,
"saves": 7,
"comments": 0,
"created_at": "2026-02-28",
"updated_at": "2026-02-28",
"url": "https://openwebui.com/posts/openwebui_skills_manager_tool_b4bce8e4"
},
{
"title": "导出为Word增强版",
"slug": "导出为_word_支持公式流程图表格和代码块_8a6306c0",
@@ -182,8 +199,8 @@
"version": "0.4.4",
"author": "Fu-Jie",
"description": "将对话导出为 Word (.docx),支持 Mermaid 图表 (客户端渲染 SVG+PNG)、LaTeX 数学公式、真实超链接、增强表格格式、代码高亮和引用块。",
"downloads": 153,
"views": 2631,
"downloads": 157,
"views": 2732,
"upvotes": 14,
"saves": 7,
"comments": 4,
@@ -198,8 +215,8 @@
"version": "0.1.0",
"author": "Fu-Jie",
"description": "Automatically extracts project rules from conversations and injects them into the folder's system prompt.",
"downloads": 99,
"views": 1839,
"downloads": 106,
"views": 1911,
"upvotes": 7,
"saves": 11,
"comments": 0,
@@ -207,6 +224,22 @@
"updated_at": "2026-01-20",
"url": "https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2"
},
{
"title": "GitHub Copilot SDK Files Filter",
"slug": "github_copilot_sdk_files_filter_403a62ee",
"type": "filter",
"version": "0.1.3",
"author": "Fu-Jie",
"description": "A specialized filter to bypass OpenWebUI's default RAG for GitHub Copilot SDK models. It moves uploaded files to a safe location ('copilot_files') so the Copilot Pipe can process them natively without interference.",
"downloads": 69,
"views": 2231,
"upvotes": 4,
"saves": 1,
"comments": 0,
"created_at": "2026-02-09",
"updated_at": "2026-02-26",
"url": "https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee"
},
{
"title": "智能信息图",
"slug": "智能信息图_e04a48ff",
@@ -215,7 +248,7 @@
"author": "Fu-Jie",
"description": "基于 AntV Infographic 的智能信息图生成插件。支持多种专业模板,自动图标匹配,并提供 SVG/PNG 下载功能。",
"downloads": 65,
"views": 1304,
"views": 1370,
"upvotes": 10,
"saves": 1,
"comments": 0,
@@ -223,22 +256,6 @@
"updated_at": "2026-02-13",
"url": "https://openwebui.com/posts/智能信息图_e04a48ff"
},
{
"title": "GitHub Copilot SDK Files Filter",
"slug": "github_copilot_sdk_files_filter_403a62ee",
"type": "filter",
"version": "0.1.3",
"author": "Fu-Jie",
"description": "A specialized filter to bypass OpenWebUI's default RAG for GitHub Copilot SDK models. It moves uploaded files to a safe location ('copilot_files') so the Copilot Pipe can process them natively without interference.",
"downloads": 54,
"views": 2098,
"upvotes": 3,
"saves": 1,
"comments": 0,
"created_at": "2026-02-09",
"updated_at": "2026-02-25",
"url": "https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee"
},
{
"title": "思维导图",
"slug": "智能生成交互式思维导图帮助用户可视化知识_8d4b097b",
@@ -246,8 +263,8 @@
"version": "0.9.2",
"author": "Fu-Jie",
"description": "智能分析文本内容,生成交互式思维导图,帮助用户结构化和可视化知识。",
"downloads": 45,
"views": 691,
"downloads": 50,
"views": 734,
"upvotes": 6,
"saves": 2,
"comments": 0,
@@ -263,7 +280,7 @@
"author": "Fu-Jie",
"description": "通过智能摘要和消息压缩,降低长对话的 token 消耗,同时保持对话连贯性。",
"downloads": 38,
"views": 783,
"views": 814,
"upvotes": 7,
"saves": 5,
"comments": 0,
@@ -278,8 +295,8 @@
"version": "0.2.4",
"author": "Fu-Jie",
"description": "快速将文本提炼为精美的学习记忆卡片,支持核心要点提取与分类。",
"downloads": 32,
"views": 830,
"downloads": 33,
"views": 863,
"upvotes": 7,
"saves": 1,
"comments": 0,
@@ -294,8 +311,8 @@
"version": "1.0.0",
"author": "Fu-Jie",
"description": "全方位的思维透镜 —— 从背景全景到逻辑脉络,从深度洞察到行动路径。",
"downloads": 26,
"views": 581,
"downloads": 29,
"views": 626,
"upvotes": 5,
"saves": 1,
"comments": 0,
@@ -304,51 +321,35 @@
"url": "https://openwebui.com/posts/精读_99830b0f"
},
{
"title": "🚀 GitHub Copilot SDK Pipe v0.9.0: Copilot SDK Skills Core Capabilities & Extended Delivery",
"title": "🚀 GitHub Copilot SDK Pipe v0.9.0: Skills & RichUI",
"slug": "github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452",
"type": "post",
"version": "",
"author": "",
"description": "",
"downloads": 0,
"views": 7,
"upvotes": 0,
"saves": 0,
"views": 1162,
"upvotes": 5,
"saves": 1,
"comments": 0,
"created_at": "2026-02-27",
"updated_at": "2026-02-27",
"created_at": "2026-02-28",
"updated_at": "2026-02-28",
"url": "https://openwebui.com/posts/github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452"
},
{
"title": "🚀 GitHub Copilot SDK Pipe v0.8.0: Conditional Tool Filtering & Publish Reliability 🎛",
"slug": "github_copilot_sdk_pipe_v080_conditional_tool_filt_a5a3322d",
"type": "post",
"version": "",
"author": "",
"description": "",
"downloads": 0,
"views": 1059,
"upvotes": 2,
"saves": 2,
"comments": 0,
"created_at": "2026-02-25",
"updated_at": "2026-02-25",
"url": "https://openwebui.com/posts/github_copilot_sdk_pipe_v080_conditional_tool_filt_a5a3322d"
},
{
"title": "🚀 GitHub Copilot SDK Pipe v0.7.0: Native Tool UI & Zero-Config CLI 🛠️",
"title": "🚀 GitHub Copilot SDK Pipe v0.7.0: Skills & Rich UI 🛠",
"slug": "github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131",
"type": "post",
"version": "",
"author": "",
"description": "",
"downloads": 0,
"views": 2162,
"upvotes": 7,
"views": 2504,
"upvotes": 8,
"saves": 2,
"comments": 1,
"created_at": "2026-02-22",
"updated_at": "2026-02-22",
"created_at": "2026-02-23",
"updated_at": "2026-02-28",
"url": "https://openwebui.com/posts/github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131"
},
{
@@ -359,7 +360,7 @@
"author": "",
"description": "",
"downloads": 0,
"views": 2257,
"views": 2341,
"upvotes": 7,
"saves": 4,
"comments": 0,
@@ -375,12 +376,12 @@
"author": "",
"description": "",
"downloads": 0,
"views": 1839,
"views": 1887,
"upvotes": 12,
"saves": 19,
"saves": 21,
"comments": 8,
"created_at": "2026-01-25",
"updated_at": "2026-01-28",
"updated_at": "2026-01-29",
"url": "https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e"
},
{
@@ -391,7 +392,7 @@
"author": "",
"description": "",
"downloads": 0,
"views": 234,
"views": 246,
"upvotes": 2,
"saves": 0,
"comments": 0,
@@ -407,9 +408,9 @@
"author": "",
"description": "",
"downloads": 0,
"views": 1502,
"views": 1531,
"upvotes": 16,
"saves": 11,
"saves": 12,
"comments": 2,
"created_at": "2026-01-10",
"updated_at": "2026-01-10",
@@ -421,10 +422,10 @@
"name": "Fu-Jie",
"profile_url": "https://openwebui.com/u/Fu-Jie",
"profile_image": "https://community.s3.openwebui.com/uploads/users/b15d1348-4347-42b4-b815-e053342d6cb0/profile_d9510745-4bd4-4f8f-a997-4a21847d9300.webp",
"followers": 295,
"followers": 307,
"following": 6,
"total_points": 299,
"post_points": 251,
"total_points": 319,
"post_points": 271,
"comment_points": 48,
"contributions": 54
}

View File

@@ -8,7 +8,7 @@
> *Blue: Downloads | Purple: Views (Real-time dynamic)*
### 📂 Content Distribution
![Distribution](https://kroki.io/mermaid/svg/eNpNi0EOQDAQRfdOMekNiFhYO4CFCyBDflI6aafE7Sld-Lv38p-ASaGWyfQ2rthpuIQDdQjqMUWF201Bz4y4oIZaajJCOGH54Ti_1ySqzyywyj6ZOhfebaK_xvMBPl9xA49YJGU=)
![Distribution](https://kroki.io/mermaid/svg/eNpNjEEOQEAMRfdO0cwNCBtrB7BwAaTkJ0ObmSJuz2Dh795r_1cwGcwzudZvM1bqTuVIDaIFDJtBVpfRHWci3lFN-Ysq0RJWH0L5d53gjUMS5Sv68ZlKH8XXCLKo_TqBd_DxiAuzSCeb)
## 📈 Overview
@@ -25,10 +25,11 @@
## 📂 By Type
- ![post](https://img.shields.io/badge/post-6-blue)
- ![tool](https://img.shields.io/badge/tool-1-teal)
- ![post](https://img.shields.io/badge/post-5-blue)
- ![pipe](https://img.shields.io/badge/pipe-1-blueviolet)
- ![action](https://img.shields.io/badge/action-12-orange)
- ![filter](https://img.shields.io/badge/filter-4-brightgreen)
- ![action](https://img.shields.io/badge/action-12-orange)
- ![prompt](https://img.shields.io/badge/prompt-1-lightgrey)
- ![review](https://img.shields.io/badge/review-1-yellow)
@@ -36,28 +37,28 @@
| Rank | Title | Type | Version | Downloads | Views | Upvotes | Saves | Updated |
|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_dl.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_vw.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_up.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_sv.json&style=flat) | 2026-02-27 |
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_dl.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_vw.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_up.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_sv.json&style=flat) | 2026-02-28 |
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![post_376135b87514e63570283b2057459b1f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_dl.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_vw.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_up.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_sv.json&style=flat) | 2026-02-13 |
| 3 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | filter | ![v](https://img.shields.io/badge/v-1.2.7-blue?style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_dl.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_vw.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_up.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_sv.json&style=flat) | 2026-02-27 |
| 3 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | filter | ![v](https://img.shields.io/badge/v-1.2.7-blue?style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_dl.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_vw.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_up.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_sv.json&style=flat) | 2026-02-28 |
| 4 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![post_dc072f01690dc8293384153dc0231d05_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_dl.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_vw.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_up.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_sv.json&style=flat) | 2026-02-13 |
| 5 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | filter | ![v](https://img.shields.io/badge/v-1.3.0-blue?style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_dl.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_vw.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_up.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_sv.json&style=flat) | 2026-02-21 |
| 5 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | filter | ![v](https://img.shields.io/badge/v-1.3.0-blue?style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_dl.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_vw.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_up.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_sv.json&style=flat) | 2026-02-28 |
| 6 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | ![v](https://img.shields.io/badge/v-0.3.7-blue?style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_dl.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_vw.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_up.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_sv.json&style=flat) | 2026-02-13 |
| 7 | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | prompt | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_dl.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_vw.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_up.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_sv.json&style=flat) | 2026-01-28 |
| 8 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | ![v](https://img.shields.io/badge/v-0.2.4-blue?style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_dl.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_vw.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_up.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_sv.json&style=flat) | 2026-02-13 |
| 9 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | ![v](https://img.shields.io/badge/v-0.9.0-blue?style=flat) | ![post_aef940e01073e811a311c3a443d9c149_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_dl.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_vw.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_up.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_sv.json&style=flat) | 2026-02-27 |
| 8 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | ![v](https://img.shields.io/badge/v-0.9.0-blue?style=flat) | ![post_aef940e01073e811a311c3a443d9c149_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_dl.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_vw.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_up.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_sv.json&style=flat) | 2026-02-28 |
| 9 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | ![v](https://img.shields.io/badge/v-0.2.4-blue?style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_dl.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_vw.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_up.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_sv.json&style=flat) | 2026-02-13 |
| 10 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_dl.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_vw.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_up.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_sv.json&style=flat) | 2026-01-08 |
| 11 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_dl.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_vw.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_up.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_sv.json&style=flat) | 2026-02-13 |
| 12 | [📂 Folder Memory Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | ![v](https://img.shields.io/badge/v-0.1.0-blue?style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_dl.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_vw.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_up.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_sv.json&style=flat) | 2026-01-20 |
| 13 | [智能信息图](https://openwebui.com/posts/智能信息图_e04a48ff) | action | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_dl.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_vw.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_up.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_sv.json&style=flat) | 2026-02-13 |
| 14 | [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee) | filter | ![v](https://img.shields.io/badge/v-0.1.3-blue?style=flat) | ![post_68081377a06a5746efe798136a72c6b6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_dl.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_vw.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_up.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_sv.json&style=flat) | 2026-02-25 |
| 15 | [思维导](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | ![v](https://img.shields.io/badge/v-0.9.2-blue?style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_dl.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_vw.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_up.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_sv.json&style=flat) | 2026-02-13 |
| 16 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | ![v](https://img.shields.io/badge/v-1.2.2-blue?style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_dl.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_vw.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_up.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_sv.json&style=flat) | 2026-02-13 |
| 17 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | ![v](https://img.shields.io/badge/v-0.2.4-blue?style=flat) | ![post_c68a2593e76ec324361358575b6501de_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_dl.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_vw.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_up.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_sv.json&style=flat) | 2026-02-13 |
| 18 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_dl.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_vw.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_up.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_sv.json&style=flat) | 2026-01-08 |
| 19 | [🚀 GitHub Copilot SDK Pipe v0.9.0: Copilot SDK Skills Core Capabilities & Extended Delivery](https://openwebui.com/posts/github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_dl.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_vw.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_up.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_sv.json&style=flat) | 2026-02-27 |
| 20 | [🚀 GitHub Copilot SDK Pipe v0.8.0: Conditional Tool Filtering & Publish Reliability 🎛️](https://openwebui.com/posts/github_copilot_sdk_pipe_v080_conditional_tool_filt_a5a3322d) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_7fdac7ddf86cc4cc7769319f237deff8_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_7fdac7ddf86cc4cc7769319f237deff8_dl.json&style=flat) | ![post_7fdac7ddf86cc4cc7769319f237deff8_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_7fdac7ddf86cc4cc7769319f237deff8_vw.json&style=flat) | ![post_7fdac7ddf86cc4cc7769319f237deff8_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_7fdac7ddf86cc4cc7769319f237deff8_up.json&style=flat) | ![post_7fdac7ddf86cc4cc7769319f237deff8_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_7fdac7ddf86cc4cc7769319f237deff8_sv.json&style=flat) | 2026-02-25 |
| 21 | [🚀 GitHub Copilot SDK Pipe v0.7.0: Native Tool UI & Zero-Config CLI 🛠️](https://openwebui.com/posts/github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_dl.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_vw.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_up.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_sv.json&style=flat) | 2026-02-22 |
| 11 | [OpenWebUI Skills Manager Tool](https://openwebui.com/posts/openwebui_skills_manager_tool_b4bce8e4) | tool | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_dl.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_vw.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_up.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_sv.json&style=flat) | 2026-02-28 |
| 12 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_dl.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_vw.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_up.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_sv.json&style=flat) | 2026-02-13 |
| 13 | [📂 Folder Memory Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | ![v](https://img.shields.io/badge/v-0.1.0-blue?style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_dl.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_vw.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_up.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_sv.json&style=flat) | 2026-01-20 |
| 14 | [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee) | filter | ![v](https://img.shields.io/badge/v-0.1.3-blue?style=flat) | ![post_68081377a06a5746efe798136a72c6b6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_dl.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_vw.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_up.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_sv.json&style=flat) | 2026-02-26 |
| 15 | [智能信息](https://openwebui.com/posts/智能信息图_e04a48ff) | action | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_dl.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_vw.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_up.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_sv.json&style=flat) | 2026-02-13 |
| 16 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | ![v](https://img.shields.io/badge/v-0.9.2-blue?style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_dl.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_vw.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_up.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_sv.json&style=flat) | 2026-02-13 |
| 17 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | ![v](https://img.shields.io/badge/v-1.2.2-blue?style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_dl.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_vw.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_up.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_sv.json&style=flat) | 2026-02-13 |
| 18 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | ![v](https://img.shields.io/badge/v-0.2.4-blue?style=flat) | ![post_c68a2593e76ec324361358575b6501de_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_dl.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_vw.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_up.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_sv.json&style=flat) | 2026-02-13 |
| 19 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_dl.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_vw.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_up.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_sv.json&style=flat) | 2026-01-08 |
| 20 | [🚀 GitHub Copilot SDK Pipe v0.9.0: Skills & RichUI](https://openwebui.com/posts/github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_dl.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_vw.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_up.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_sv.json&style=flat) | 2026-02-28 |
| 21 | [🚀 GitHub Copilot SDK Pipe v0.7.0: Skills & Rich UI 🛠️](https://openwebui.com/posts/github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_dl.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_vw.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_up.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_sv.json&style=flat) | 2026-02-28 |
| 22 | [🚀 GitHub Copilot SDK Pipe: AI That Executes, Not Just Talks](https://openwebui.com/posts/github_copilot_sdk_for_openwebui_elevate_your_ai_t_a140f293) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_dl.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_vw.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_up.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_sv.json&style=flat) | 2026-02-10 |
| 23 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_858cb162732370288ce024b4f7944f69_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_dl.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_vw.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_up.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_sv.json&style=flat) | 2026-01-28 |
| 23 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_858cb162732370288ce024b4f7944f69_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_dl.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_vw.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_up.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_sv.json&style=flat) | 2026-01-29 |
| 24 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | review | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_dl.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_vw.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_up.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_sv.json&style=flat) | 2026-01-14 |
| 25 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_dl.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_vw.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_up.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_sv.json&style=flat) | 2026-01-10 |

View File

@@ -8,7 +8,7 @@
> *蓝色: 总下载量 | 紫色: 总浏览量 (实时动态生成)*
### 📂 内容分类占比 (Distribution)
![Distribution](https://kroki.io/mermaid/svg/eNpNi0EOQDAQRfdOMekNiFhYO4CFCyBDflI6aafE7Sld-Lv38p-ASaGWyfQ2rthpuIQDdQjqMUWF201Bz4y4oIZaajJCOGH54Ti_1ySqzyywyj6ZOhfebaK_xvMBPl9xA49YJGU=)
![Distribution](https://kroki.io/mermaid/svg/eNpNjEEOQEAMRfdO0cwNCBtrB7BwAaTkJ0ObmSJuz2Dh795r_1cwGcwzudZvM1bqTuVIDaIFDJtBVpfRHWci3lFN-Ysq0RJWH0L5d53gjUMS5Sv68ZlKH8XXCLKo_TqBd_DxiAuzSCeb)
## 📈 总览
@@ -25,10 +25,11 @@
## 📂 按类型分类
- ![post](https://img.shields.io/badge/post-6-blue)
- ![tool](https://img.shields.io/badge/tool-1-teal)
- ![post](https://img.shields.io/badge/post-5-blue)
- ![pipe](https://img.shields.io/badge/pipe-1-blueviolet)
- ![action](https://img.shields.io/badge/action-12-orange)
- ![filter](https://img.shields.io/badge/filter-4-brightgreen)
- ![action](https://img.shields.io/badge/action-12-orange)
- ![prompt](https://img.shields.io/badge/prompt-1-lightgrey)
- ![review](https://img.shields.io/badge/review-1-yellow)
@@ -36,28 +37,28 @@
| 排名 | 标题 | 类型 | 版本 | 下载 | 浏览 | 点赞 | 收藏 | 更新日期 |
|:---:|------|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_dl.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_vw.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_up.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_sv.json&style=flat) | 2026-02-27 |
| 1 | [Smart Mind Map](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_dl.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_vw.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_up.json&style=flat) | ![post_207cd433d27fdd853ccbfa941e6fa67f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_207cd433d27fdd853ccbfa941e6fa67f_sv.json&style=flat) | 2026-02-28 |
| 2 | [Smart Infographic](https://openwebui.com/posts/smart_infographic_ad6f0c7f) | action | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![post_376135b87514e63570283b2057459b1f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_dl.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_vw.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_up.json&style=flat) | ![post_376135b87514e63570283b2057459b1f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_376135b87514e63570283b2057459b1f_sv.json&style=flat) | 2026-02-13 |
| 3 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | filter | ![v](https://img.shields.io/badge/v-1.2.7-blue?style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_dl.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_vw.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_up.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_sv.json&style=flat) | 2026-02-27 |
| 3 | [Markdown Normalizer](https://openwebui.com/posts/markdown_normalizer_baaa8732) | filter | ![v](https://img.shields.io/badge/v-1.2.7-blue?style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_dl.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_vw.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_up.json&style=flat) | ![post_a963da7c5d914310e7026b8c8a6635b0_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a963da7c5d914310e7026b8c8a6635b0_sv.json&style=flat) | 2026-02-28 |
| 4 | [Export to Word Enhanced](https://openwebui.com/posts/export_to_word_enhanced_formatting_fca6a315) | action | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![post_dc072f01690dc8293384153dc0231d05_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_dl.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_vw.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_up.json&style=flat) | ![post_dc072f01690dc8293384153dc0231d05_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_dc072f01690dc8293384153dc0231d05_sv.json&style=flat) | 2026-02-13 |
| 5 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | filter | ![v](https://img.shields.io/badge/v-1.3.0-blue?style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_dl.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_vw.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_up.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_sv.json&style=flat) | 2026-02-21 |
| 5 | [Async Context Compression](https://openwebui.com/posts/async_context_compression_b1655bc8) | filter | ![v](https://img.shields.io/badge/v-1.3.0-blue?style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_dl.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_vw.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_up.json&style=flat) | ![post_0640a7ef0970872217cb939f2ba9c12c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0640a7ef0970872217cb939f2ba9c12c_sv.json&style=flat) | 2026-02-28 |
| 6 | [Export to Excel](https://openwebui.com/posts/export_mulit_table_to_excel_244b8f9d) | action | ![v](https://img.shields.io/badge/v-0.3.7-blue?style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_dl.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_vw.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_up.json&style=flat) | ![post_c5ae05d46c7b999a1e36ca1457f1926b_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5ae05d46c7b999a1e36ca1457f1926b_sv.json&style=flat) | 2026-02-13 |
| 7 | [AI Task Instruction Generator](https://openwebui.com/posts/ai_task_instruction_generator_9bab8b37) | prompt | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_dl.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_vw.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_up.json&style=flat) | ![post_19f469a02b32d21f86b16d1395a81317_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_19f469a02b32d21f86b16d1395a81317_sv.json&style=flat) | 2026-01-28 |
| 8 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | ![v](https://img.shields.io/badge/v-0.2.4-blue?style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_dl.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_vw.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_up.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_sv.json&style=flat) | 2026-02-13 |
| 9 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | ![v](https://img.shields.io/badge/v-0.9.0-blue?style=flat) | ![post_aef940e01073e811a311c3a443d9c149_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_dl.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_vw.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_up.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_sv.json&style=flat) | 2026-02-27 |
| 8 | [GitHub Copilot Official SDK Pipe](https://openwebui.com/posts/github_copilot_official_sdk_pipe_ce96f7b4) | pipe | ![v](https://img.shields.io/badge/v-0.9.0-blue?style=flat) | ![post_aef940e01073e811a311c3a443d9c149_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_dl.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_vw.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_up.json&style=flat) | ![post_aef940e01073e811a311c3a443d9c149_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_aef940e01073e811a311c3a443d9c149_sv.json&style=flat) | 2026-02-28 |
| 9 | [Flash Card](https://openwebui.com/posts/flash_card_65a2ea8f) | action | ![v](https://img.shields.io/badge/v-0.2.4-blue?style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_dl.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_vw.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_up.json&style=flat) | ![post_5cf7a5dad74ef15ee4ffc6fe10da8213_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_5cf7a5dad74ef15ee4ffc6fe10da8213_sv.json&style=flat) | 2026-02-13 |
| 10 | [Deep Dive](https://openwebui.com/posts/deep_dive_c0b846e4) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_dl.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_vw.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_up.json&style=flat) | ![post_78e3480de4e6bd4615498495c5d15979_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_78e3480de4e6bd4615498495c5d15979_sv.json&style=flat) | 2026-01-08 |
| 11 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_dl.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_vw.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_up.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_sv.json&style=flat) | 2026-02-13 |
| 12 | [📂 Folder Memory Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | ![v](https://img.shields.io/badge/v-0.1.0-blue?style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_dl.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_vw.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_up.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_sv.json&style=flat) | 2026-01-20 |
| 13 | [智能信息图](https://openwebui.com/posts/智能信息图_e04a48ff) | action | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_dl.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_vw.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_up.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_sv.json&style=flat) | 2026-02-13 |
| 14 | [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee) | filter | ![v](https://img.shields.io/badge/v-0.1.3-blue?style=flat) | ![post_68081377a06a5746efe798136a72c6b6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_dl.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_vw.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_up.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_sv.json&style=flat) | 2026-02-25 |
| 15 | [思维导](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | ![v](https://img.shields.io/badge/v-0.9.2-blue?style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_dl.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_vw.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_up.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_sv.json&style=flat) | 2026-02-13 |
| 16 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | ![v](https://img.shields.io/badge/v-1.2.2-blue?style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_dl.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_vw.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_up.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_sv.json&style=flat) | 2026-02-13 |
| 17 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | ![v](https://img.shields.io/badge/v-0.2.4-blue?style=flat) | ![post_c68a2593e76ec324361358575b6501de_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_dl.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_vw.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_up.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_sv.json&style=flat) | 2026-02-13 |
| 18 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_dl.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_vw.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_up.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_sv.json&style=flat) | 2026-01-08 |
| 19 | [🚀 GitHub Copilot SDK Pipe v0.9.0: Copilot SDK Skills Core Capabilities & Extended Delivery](https://openwebui.com/posts/github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_dl.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_vw.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_up.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_sv.json&style=flat) | 2026-02-27 |
| 20 | [🚀 GitHub Copilot SDK Pipe v0.8.0: Conditional Tool Filtering & Publish Reliability 🎛️](https://openwebui.com/posts/github_copilot_sdk_pipe_v080_conditional_tool_filt_a5a3322d) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_7fdac7ddf86cc4cc7769319f237deff8_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_7fdac7ddf86cc4cc7769319f237deff8_dl.json&style=flat) | ![post_7fdac7ddf86cc4cc7769319f237deff8_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_7fdac7ddf86cc4cc7769319f237deff8_vw.json&style=flat) | ![post_7fdac7ddf86cc4cc7769319f237deff8_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_7fdac7ddf86cc4cc7769319f237deff8_up.json&style=flat) | ![post_7fdac7ddf86cc4cc7769319f237deff8_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_7fdac7ddf86cc4cc7769319f237deff8_sv.json&style=flat) | 2026-02-25 |
| 21 | [🚀 GitHub Copilot SDK Pipe v0.7.0: Native Tool UI & Zero-Config CLI 🛠️](https://openwebui.com/posts/github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_dl.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_vw.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_up.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_sv.json&style=flat) | 2026-02-22 |
| 11 | [OpenWebUI Skills Manager Tool](https://openwebui.com/posts/openwebui_skills_manager_tool_b4bce8e4) | tool | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_dl.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_vw.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_up.json&style=flat) | ![post_6e9b698faed2ca2ba6f8308635a4804e_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_6e9b698faed2ca2ba6f8308635a4804e_sv.json&style=flat) | 2026-02-28 |
| 12 | [导出为Word增强版](https://openwebui.com/posts/导出为_word_支持公式流程图表格和代码块_8a6306c0) | action | ![v](https://img.shields.io/badge/v-0.4.4-blue?style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_dl.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_vw.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_up.json&style=flat) | ![post_fc4cbdf0eb78bd98c2ed7650d459b59e_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fc4cbdf0eb78bd98c2ed7650d459b59e_sv.json&style=flat) | 2026-02-13 |
| 13 | [📂 Folder Memory Auto-Evolving Project Context](https://openwebui.com/posts/folder_memory_auto_evolving_project_context_4a9875b2) | filter | ![v](https://img.shields.io/badge/v-0.1.0-blue?style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_dl.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_vw.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_up.json&style=flat) | ![post_a125b3a3702a07c86c77668dcede2149_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_a125b3a3702a07c86c77668dcede2149_sv.json&style=flat) | 2026-01-20 |
| 14 | [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/github_copilot_sdk_files_filter_403a62ee) | filter | ![v](https://img.shields.io/badge/v-0.1.3-blue?style=flat) | ![post_68081377a06a5746efe798136a72c6b6_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_dl.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_vw.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_up.json&style=flat) | ![post_68081377a06a5746efe798136a72c6b6_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_68081377a06a5746efe798136a72c6b6_sv.json&style=flat) | 2026-02-26 |
| 15 | [智能信息](https://openwebui.com/posts/智能信息图_e04a48ff) | action | ![v](https://img.shields.io/badge/v-1.5.0-blue?style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_dl.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_vw.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_up.json&style=flat) | ![post_0c3f8621f77a9d5875d3c26ee38a3954_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0c3f8621f77a9d5875d3c26ee38a3954_sv.json&style=flat) | 2026-02-13 |
| 16 | [思维导图](https://openwebui.com/posts/智能生成交互式思维导图帮助用户可视化知识_8d4b097b) | action | ![v](https://img.shields.io/badge/v-0.9.2-blue?style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_dl.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_vw.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_up.json&style=flat) | ![post_c5bfbfd0d29694a04c597e26202b824f_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c5bfbfd0d29694a04c597e26202b824f_sv.json&style=flat) | 2026-02-13 |
| 17 | [异步上下文压缩](https://openwebui.com/posts/异步上下文压缩_5c0617cb) | action | ![v](https://img.shields.io/badge/v-1.2.2-blue?style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_dl.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_vw.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_up.json&style=flat) | ![post_fa447e583483020bc1bf35e74302c75c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_fa447e583483020bc1bf35e74302c75c_sv.json&style=flat) | 2026-02-13 |
| 18 | [闪记卡 (Flash Card)](https://openwebui.com/posts/闪记卡生成插件_4a31eac3) | action | ![v](https://img.shields.io/badge/v-0.2.4-blue?style=flat) | ![post_c68a2593e76ec324361358575b6501de_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_dl.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_vw.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_up.json&style=flat) | ![post_c68a2593e76ec324361358575b6501de_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_c68a2593e76ec324361358575b6501de_sv.json&style=flat) | 2026-02-13 |
| 19 | [精读](https://openwebui.com/posts/精读_99830b0f) | action | ![v](https://img.shields.io/badge/v-1.0.0-blue?style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_dl.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_vw.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_up.json&style=flat) | ![post_14a3b01bc6559558b48a61a56dd3635d_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_14a3b01bc6559558b48a61a56dd3635d_sv.json&style=flat) | 2026-01-08 |
| 20 | [🚀 GitHub Copilot SDK Pipe v0.9.0: Skills & RichUI](https://openwebui.com/posts/github_copilot_sdk_pipe_v090_copilot_sdk_skills_co_99a42452) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_dl.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_vw.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_up.json&style=flat) | ![post_0f2ccf8471a3db168298f4f1e86ba0f4_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_0f2ccf8471a3db168298f4f1e86ba0f4_sv.json&style=flat) | 2026-02-28 |
| 21 | [🚀 GitHub Copilot SDK Pipe v0.7.0: Skills & Rich UI 🛠️](https://openwebui.com/posts/github_copilot_sdk_pipe_v070_native_tool_ui_zero_c_4af38131) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_dl.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_vw.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_up.json&style=flat) | ![post_8aad0d77dbeaa0bdd3e6971274b5fa5c_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8aad0d77dbeaa0bdd3e6971274b5fa5c_sv.json&style=flat) | 2026-02-28 |
| 22 | [🚀 GitHub Copilot SDK Pipe: AI That Executes, Not Just Talks](https://openwebui.com/posts/github_copilot_sdk_for_openwebui_elevate_your_ai_t_a140f293) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_dl.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_vw.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_up.json&style=flat) | ![post_42a11f4f094a0fa757bfefece51b1180_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_42a11f4f094a0fa757bfefece51b1180_sv.json&style=flat) | 2026-02-10 |
| 23 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_858cb162732370288ce024b4f7944f69_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_dl.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_vw.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_up.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_sv.json&style=flat) | 2026-01-28 |
| 23 | [🚀 Open WebUI Prompt Plus: AI-Powered Prompt Manager](https://openwebui.com/posts/open_webui_prompt_plus_ai_powered_prompt_manager_s_15fa060e) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_858cb162732370288ce024b4f7944f69_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_dl.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_vw.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_up.json&style=flat) | ![post_858cb162732370288ce024b4f7944f69_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_858cb162732370288ce024b4f7944f69_sv.json&style=flat) | 2026-01-29 |
| 24 | [Review of Claude Haiku 4.5](https://openwebui.com/posts/review_of_claude_haiku_45_41b0db39) | review | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_dl.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_vw.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_up.json&style=flat) | ![post_8db923aec1f58d282a2334db388aa5c2_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_8db923aec1f58d282a2334db388aa5c2_sv.json&style=flat) | 2026-01-14 |
| 25 | [ 🛠️ Debug Open WebUI Plugins in Your Browser](https://openwebui.com/posts/debug_open_webui_plugins_in_your_browser_81bf7960) | post | ![v](https://img.shields.io/badge/v-N/A-gray?style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_dl](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_dl.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_vw](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_vw.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_up](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_up.json&style=flat) | ![post_e38b91e43262d29106345acfcd9dffba_sv](https://img.shields.io/endpoint?url=https%3A%2F%2Fgist.githubusercontent.com%2FFu-Jie%2Fdb3d95687075a880af6f1fba76d679c6%2Fraw%2Fbadge_post_e38b91e43262d29106345acfcd9dffba_sv.json&style=flat) | 2026-01-10 |

View File

@@ -1,8 +1,14 @@
# GitHub Copilot SDK Pipe for OpenWebUI
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.9.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.9.1 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/open-webui) that integrates the official [GitHub Copilot SDK](https://github.com/github/copilot-sdk). It enables you to use **GitHub Copilot models** (e.g., `gpt-5.2-codex`, `claude-sonnet-4.5`,`gemini-3-pro`, `gpt-5-mini`) **AND** your own models via **BYOK** (OpenAI, Anthropic) directly within OpenWebUI, providing a unified agentic experience with **strict User & Chat-level Workspace Isolation**.
This is a powerful **GitHub Copilot SDK** Pipe for **OpenWebUI** that provides a unified **Agentic experience**. It goes beyond simple model access by enabling autonomous **Intent Recognition**, **Web Search**, and **Context Compaction**. It seamlessly reuses your existing **Tools, MCP servers, OpenAPI servers, and Skills** from OpenWebUI to create a truly integrated ecosystem.
- **🧠 Autonomous Intent Recognition**: The Agent independently analyzes user goals to determine the most effective path forward.
- **🌐 Smart Web Search**: Built-in capability to trigger web searches autonomously based on task requirements.
- **♾️ Infinite Session (Context Compaction)**: Automatically manages long-running conversations by compacting context (summarization + TODO persistence) to maintain project focus.
- **🧩 Ecosystem Injection**: Directly reads and leverages your configured **OpenWebUI Tools, MCPs, OpenAPI Servers, and Skills**.
- **🎨 Interactive Delivery**: Native support for **HTML Artifacts** and **RichUI** components for real-time visualization and reporting.
> [!IMPORTANT]
> **Essential Companion**
@@ -14,19 +20,19 @@ This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/
---
## ✨ v0.9.0: The Skills Revolution & Stability Update
## ✨ v0.9.1: Autonomous Web Search & Reliability Fix
- **🧩 Copilot SDK Skills Support**: Native support for Copilot SDK skill directories (`SKILL.md` + resources).
- **🔄 OpenWebUI Skills Bridge**: Full bidirectional sync between OpenWebUI Workspace > Skills and SDK skill directories.
- **🛠️ Deterministic `manage_skills` Tool**: Expert tool for stable install/create/list/edit/delete skill operations.
- **🌊 Reinforced Status Bar**: Multi-layered locking mechanism (`session_finalized` guard) and atomic async delivery to prevent "stuck" indicators.
- **🗂️ Persistent Config Directory**: Added `COPILOTSDK_CONFIG_DIR` for stable session-state persistence across container restarts.
- **🌐 Autonomous Web Search**: `web_search` is now always enabled for the Agent (bypassing the UI toggle), leveraging the Copilot SDK's native ability to decide when to search.
- **🛠️ Terminology Alignment**: Standardized all references to **"Agent"** and **"Context Compaction"** (for Infinite Session) across all languages to better reflect the technical capabilities.
- **🌐 Language Consistency**: System prompts mandate that Agent output language remains strictly consistent with user input.
- **🐛 Fixed MCP Tool Filtering**: Resolved a critical issue where configuring `function_name_filter_list` (or selecting specific tools in UI) would cause all tools from that MCP server to be incorrectly hidden due to ID prefix mismatches (`server:mcp:`).
- **🔍 Improved Filter Stability**: Ensured tool-level whitelists apply reliably without breaking the entire server connection.
---
## ✨ Key Capabilities
- **🔑 Unified Intelligence (Official + BYOK)**: Seamlessly switch between official GitHub Copilot models (o1, GPT-4o, Claude 3.5 Sonnet, Gemini 2.0 Flash) and your own models (OpenAI, Anthropic) via **Bring Your Own Key** mode.
- **🔑 Unified Intelligence (Official + BYOK)**: Seamlessly switch between official GitHub Copilot models and your own models (OpenAI, Anthropic, DeepSeek, xAI) via **Bring Your Own Key** mode.
- **🛡️ Physical Workspace Isolation**: Every session runs in its own isolated directory sandbox. This ensures absolute data privacy and prevents cross-chat file contamination while allowing the Agent full filesystem access.
- **🔌 Universal Tool Protocol**:
- **Native MCP**: Direct, high-performance connection to Model Context Protocol servers.
@@ -38,7 +44,13 @@ This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/
- **Live HTML/JS**: Instantly render and interact with apps, dashboards, or reports generated by the Agent.
- **Persistent Publishing**: Agents can "publish" generated files (Excel, CSV, docs) to OpenWebUI's file storage, providing permanent download links.
- **🌊 UX-First Streaming**: Full support for "Thinking" processes (Chain of Thought), status indicators, and real-time progress bars for long-running tasks.
- **🧠 Deep Database Integration**: Real-time persistence of TOD·O lists and session metadata ensures your workflow state is always visible in the UI.
- **🧠 Deep Database Integration**: Real-time persistence of TODO lists and session metadata ensures your workflow state is always visible in the UI.
> [!TIP]
> **💡 Visualization Pro-Tip**
> To get the most out of **HTML Artifacts** and **RichUI**, we highly recommend asking the Agent to install the skill via its GitHub URL:
> "Install this skill: https://github.com/nicobailon/visual-explainer".
> This skill is specifically optimized for generating high-quality visual components and integrates perfectly with this Pipe.
---

View File

@@ -1,46 +1,57 @@
# GitHub Copilot SDK 官方管道
# GitHub Copilot Official SDK Pipe
**作者:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **版本:** 0.9.0 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可证:** MIT
**作者:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **版本:** 0.9.1 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可证:** MIT
这是一个用于 [OpenWebUI](https://github.com/open-webui/open-webui) 的高级 Pipe 函数,深度集成了 **GitHub Copilot SDK**。它不仅支持 **GitHub Copilot 官方模型**(如 `gpt-5.2-codex`, `claude-sonnet-4.5`, `gemini-3-pro`, `gpt-5-mini`),还支持 **BYOK (自带 Key)** 模式对接自定义服务商OpenAI, Anthropic并具备**严格的用户与会话级工作区隔离**能力,提供统一且安全的 Agent 交互体验。
这是一个**GitHub Copilot SDK** 深度集成到 **OpenWebUI** 中的强大 Agent SDK 管道。它不仅实现了 SDK 的核心功能,还支持 **智能意图识别**、**自主网页搜索** 与 **自动上下文压缩**,并能够无缝读取 OpenWebUI 已有的配置进行智能注入,让 Agent 能够具备以下能力:
- **🧠 智能意图识别**Agent 能自主分析用户任务的深层意图,决定最有效的处理路径。
- **🌐 自主网页搜索**:具备独立的网页搜索触发判断力,无需用户手动干预。
- **♾️ 自动压缩上下文**:支持 Infinite Session自动对长对话进行上下文压缩与摘要确保长期任务跟进。
- **🛠️ 全功能 Skill 体系**:完美支持本地自定义 Skill 目录,通过脚本与资源的结合实现真正的功能增强。
- **🧩 深度生态复用**:直接复用您在 OpenWebUI 中配置的各种 **工具 (Tools)**、**MCP**、**OpenAPI Server** 和 **技能 (Skills)**
为您带来更强、更完整的交互体验。
> [!IMPORTANT]
> **核心伴侣组件**
> 如需启用文件处理与数据分析能力,请务必安装 [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc24dd6)。
>
>## ✨ 0.9.0 核心更新:技能革命与稳定性加固
- **🧩 Copilot SDK Skills 原生支持**: 技能可作为一等上下文能力被加载和使用。
- **🔄 OpenWebUI Skills 桥接**: 实现 OpenWebUI **工作区 > Skills** 与 SDK 技能目录的深度双向同步。
- **🛠️ 确定性 `manage_skills` 工具**: 通过稳定工具契约完成技能的生命周期管理。
- **🌊 状态栏逻辑加固**: 引入 `session_finalized` 多层锁定机制,彻底解决任务完成后状态栏回弹或卡死的问题。
- **🗂️ 环境目录持久化**: 增强 `COPILOTSDK_CONFIG_DIR` 逻辑,确保会话状态跨容器重启稳定存在。
- **🌐 持续化共享缓存(扩展)**: 技能统一存储在 `OPENWEBUI_SKILLS_SHARED_DIR/shared/`,跨会话与容器重启复用。
- **🎯 智能意图路由(扩展)**: 自动识别技能管理请求并优先路由到 `manage_skills`,确保执行确定性。
- **🗂️ 环境目录升级**: 新增 `COPILOTSDK_CONFIG_DIR`,并自动回退到 `/app/backend/data/.copilot`,确保 SDK 配置与会话状态在容器重启后稳定持久化。
- **🧭 CLI 提示词护栏**: 系统提示词明确区分可执行的 **tools** 与不可调用的 **skills**,并要求技能生命周期操作优先走 `manage_skills`,同时强化 CLI/Python 执行规范。
> 如需启用文件处理与数据分析能力,请务必安装 [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6)。
> [!TIP]
> **BYOK 模式无需订阅**
> 如果您使用自带的 API Key (BYOK 模式对接 OpenAI/Anthropic)**您不需要 GitHub Copilot 官方订阅**。只有在访问 GitHub 官方模型时才需要订阅。
---
## ✨ 0.9.1 最新更新:自主网页搜索与可靠性修复
- **🌐 强化自主网页搜索**`web_search` 工具现已强制对 Agent 开启(绕过 UI 网页搜索开关),充分利用 Copilot 自身具备的搜索判断能力。
- **🛠️ 术语一致性优化**:全语种同步将“助手”更改为 **"Agent"**,并将“优化会话”统一为 **"压缩上下文"**,更准确地描述 Infinite Session 的技术本质。
- **🌐 语言一致性**:内置指令确保 Agent 输出语言与用户输入严格对齐,提供无缝的国际化交互体验。
- **🐛 修复 MCP 工具过滤逻辑**:解决了在管理员后端配置 `function_name_filter_list`(或在聊天界面勾选特定工具)时,因 ID 前缀(`server:mcp:`)识别逻辑错误导致工具意外失效的问题。
- **🔍 提升过滤稳定性**:修复了工具 ID 归一化逻辑,确保点选的工具白名单在 SDK 会话中精确生效。
---
## ✨ 核心能力 (Key Capabilities)
- **🔑 统一智能体验 (官方 + BYOK)**: 自由切换官方模型o1, GPT-4o, Claude 3.5 Sonnet, Gemini 2.0 Flash与自定义服务商OpenAI, Anthropic支持 **BYOK (自带 Key)** 模式。
- **🔑 统一智能体验 (官方 + BYOK)**: 自由切换官方模型与自定义服务商OpenAI, Anthropic, DeepSeek, xAI),支持 **BYOK (自带 Key)** 模式。
- **🛡️ 物理级工作区隔离**: 每个会话在独立的沙箱目录中运行。确保绝对的数据隐私,防止不同聊天间的文件污染,同时给予 Agent 完整的文件系统操作权限。
- **🔌 通用工具协议**:
- **原生 MCP**: 高性能直连 Model Context Protocol 服务器。
- **OpenAPI 桥接**: 将任何外部 REST API 一键转换为 Agent 可调用的工具。
- **OpenWebUI 原生桥接**: 零配置接入现有的 OpenWebUI 工具及内置功能(网页搜索、记忆等)。
- **🧩 OpenWebUI Skills 桥接**: 将简单的 OpenWebUI Markdown 指令转化为包含脚本、模板数据的强大 SDK 技能文件夹。
- **🧩 OpenWebUI Skills 桥接**: 将简单的 OpenWebUI Markdown 指令转化为包含脚本、模板 and 数据的强大 SDK 技能文件夹。
- **♾️ 无限会话管理**: 先进的上下文窗口管理,支持自动“压缩”(摘要提取 + TODO 列表持久化)。支持长达数周的项目跟踪而不会丢失核心上下文。
- **📊 交互式产物与发布**:
- **实时 HTML/JS**: 瞬间渲染并交互 Agent 生成的应用程序、可视化看板或报告。
- **持久化发布**: Agent 可将生成的产物Excel, CSV, 文档)发布至 OpenWebUI 文件存储,并在聊天中提供永久下载链接。
- **🌊 极致交互体验**: 完整支持深度思考过程 (Thinking Process) 流式渲染、状态指示器以及长任务实时进度条。
- **🧠 深度数据库集成**: TOD·O 列表与会话元数据的实时持久化,确保任务执行状态在 UI 上清晰可见。
- **🧠 深度数据库集成**: TODO 列表与会话元数据的实时持久化,确保任务执行状态在 UI 上清晰可见。
> [!TIP]
> **💡 增强渲染建议**
> 为了获得最精美的 **HTML Artifacts** 与 **RichUI** 效果,建议在对话中通过提供的 GitHub 链接直接命令 Agent 安装:
> “请安装此技能:<https://github.com/nicobailon/visual-explainer”。>
> 该技能专为生成高质量可视化组件而设计,能够与本 Pipe 完美协作。
---
@@ -49,162 +60,51 @@
`GitHub Copilot SDK Files Filter` 是本 Pipe 的配套插件,用于阻止 OpenWebUI 默认 RAG 在 Pipe 接手前抢先处理上传文件。
- **作用**: 将上传文件移动到 `copilot_files`,让 Pipe 能直接读取原始二进制。
- **必要性**: 若未安装,文件可能被提前解析/向量化Agent 难以拿到原始文件。
- **必要性**: 若未安装,文件可能被提前解析/向量化Agent 拿到原始文件。
- **v0.1.3 重点**:
- 修复 BYOK 模型 ID 识别(支持 `github_copilot_official_sdk_pipe.xxx` 前缀匹配)。
- 新增双通道调试日志(`show_debug_log`):后端 logger + 浏览器控制台。
---
## ⚙️ 核心配置参数 (Valves)
## 🚀 快速开始 (Quick Start)
### 1. 管理员配置 (基础设置)
1. **安装本插件**: 在 OpenWebUI 管道管理界面添加并启用。
2. **安装 [Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6)** (必须): 以获得文件处理能力。
3. **配置凭据**:
- **官方模式**: 默认即可。确保环境中安装了 `github-copilot-sdk`
- **BYOK 模式**: 填入 OpenAI/Anthropic/DeepSeek 的 Base URL 与 Key。
4. **选择模型**: 在聊天界面选择 `GitHub Copilot Official SDK Pipe` 系列模型。
5. **开始对话**: 直接上传文件或发送复杂指令。
管理员可在函数设置中定义全局默认行为。
---
| 参数 | 默认值 | 说明 |
## ⚙️ 配置参数 (Configuration Valves)
| 参数 | 默认值 | 描述 |
| :--- | :--- | :--- |
| `GH_TOKEN` | `""` | 全局 GitHub Token (需具备 'Copilot Requests' 权限)。 |
| `COPILOTSDK_CONFIG_DIR` | `""` | SDK 配置与会话状态持久化目录 (例如: `/app/backend/data/.copilot`)。 |
| `ENABLE_OPENWEBUI_TOOLS` | `True` | 启用 OpenWebUI 工具 (包括定义工具和内置工具)。 |
| `ENABLE_OPENAPI_SERVER` | `True` | 启用 OpenAPI 工具服务器连接。 |
| `ENABLE_MCP_SERVER` | `True` | 启用直接 MCP 客户端连接 (推荐)。 |
| `ENABLE_OPENWEBUI_SKILLS` | `True` | 开启与 OpenWebUI **工作区 > Skills** 的双向同步桥接。 |
| `OPENWEBUI_SKILLS_SHARED_DIR` | `/app/backend/data/cache/copilot-openwebui-skills` | OpenWebUI skills 转换后的共享缓存目录。 |
| `GITHUB_SKILLS_SOURCE_URL` | `""` | 可选 GitHub tree 地址,用于批量导入 skills例如 anthropic/skills。 |
| `DISABLED_SKILLS` | `""` | 逗号分隔的 skill 名称黑名单(如 `docs-writer,webapp-testing`。 |
| `REASONING_EFFORT` | `medium` | 推理强度low, medium, high。 |
| `SHOW_THINKING` | `True` | 显示模型推理/思考过程。 |
| `INFINITE_SESSION` | `True` | 启用无限会话 (自动上下文压缩)。 |
| `MAX_MULTIPLIER` | `1.0` | 最大允许的模型计费倍率 (0x 为仅限免费模型)。 |
| `EXCLUDE_KEYWORDS` | `""` | 排除包含这些关键字的模型 (逗号分隔)。 |
| `TIMEOUT` | `300` | 每个流数据块的超时时间 (秒)。 |
| `BYOK_TYPE` | `openai` | BYOK 服务商类型:`openai`, `anthropic`。 |
| `BYOK_BASE_URL` | `""` | BYOK 基础 URL (例如: <https://api.openai.com/v1)。> |
| `BYOK_MODELS` | `""` | BYOK 模型列表 (逗号分隔)。留空则从 API 获取。 |
| `CUSTOM_ENV_VARS` | `""` | 自定义环境变量 (JSON 格式)。 |
| `DEBUG` | `False` | 开启此项以在前端控制台输出详细调试日志。 |
### 2. 用户配置 (个人覆盖)
普通用户可在各自的个人设置中根据需要覆盖以下参数。
| 参数 | 说明 |
| :--- | :--- |
| `GH_TOKEN` | 使用个人的 GitHub Token。 |
| `REASONING_EFFORT` | 个人偏好的推理强度。 |
| `SHOW_THINKING` | 显示模型推理/思考过程。 |
| `MAX_MULTIPLIER` | 最大允许的模型计费倍率覆盖。 |
| `EXCLUDE_KEYWORDS` | 排除包含这些关键字的模型。 |
| `ENABLE_OPENWEBUI_SKILLS` | 启用将当前用户可读的全部已启用 OpenWebUI skills 转换并加载为 SDK `SKILL.md` 目录。 |
| `GITHUB_SKILLS_SOURCE_URL` | 为当前用户会话设置可选 GitHub tree 地址以批量导入 skills。 |
| `DISABLED_SKILLS` | 为当前用户会话禁用指定 skills逗号分隔。 |
| `BYOK_API_KEY` | 使用个人的 OpenAI/Anthropic API Key。 |
| `github_token` | - | GitHub Copilot 官方 Token (如果您有官方订阅且不方便本地登录时填入)。 |
| `llm_base_url` | - | BYOK 模式的基础 URL。填入后将绕过 GitHub 官方服务。 |
| `llm_api_key` | - | BYOK 模式的 API 密钥。 |
| `llm_model_id` | `gpt-4o` | 使用的模型 ID (官方、BYOK 均适用)。 |
| `workspace_root` | `./copilot_workspaces` | 所有会话沙盒的根目录。 |
| `skills_directory` | `./copilot_skills` | 自定义 SDK 技能文件夹所在的目录。 |
| `show_status` | `True` | 是否在 UI 显示 Agent 的实时运行状态和思考过程。 |
| `enable_infinite_session` | `True` | 是否开启自动上下文压缩和 TODO 列表持久化。 |
| `enable_html_artifacts` | `True` | 是否允许 Agent 生成并实时预览 HTML 应用。 |
| `enable_rich_ui` | `True` | 是否启用进度条和增强型工具调用面板。 |
---
### 🌊 细粒度反馈与流畅体验 (Fluid UX)
## 🤝 支持 (Support)
彻底告别复杂任务执行过程中的“卡顿”感:
- **🔄 实时状态气泡**: 将 SDK 内部事件(如 `turn_start`, `compaction`, `subagent_started`)直接映射为 OpenWebUI 的状态栏信息。
- **🧭 分阶段状态描述增强**: 状态栏会明确显示处理阶段(处理中、技能触发、工具执行、工具完成/失败、发布中、任务完成)。
- **⏱️ 长任务心跳提示**: 长时间处理中会周期性显示“仍在处理中(已耗时 X 秒)”,避免用户误判为卡死。
- **📈 工具执行进度追踪**: 长耗时工具(如代码分析)会在状态栏实时显示进度百分比及当前子任务描述。
- **⚡ 即时响应反馈**: 从响应开始第一秒即显示“助手正在处理您的请求...”,减少等待空窗感。
如果这个插件对你有帮助,欢迎到 [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) 点个 Star这将是我持续改进的动力感谢支持。
---
### 🛡️ 智能版本兼容
## ⚠️ 故障排除 (Troubleshooting)
插件会自动根据您的 OpenWebUI 版本调整功能集:
- **v0.8.0+**: 开启 Rich UI、实时状态气泡及集成 HTML 预览
- **旧版本**: 自动回退至标准 Markdown 代码块模式,确保最大稳定性。
---
## 🎯 典型应用场景 (Use Cases)
- **📁 全自主仓库维护**: Agent 在隔离工作区内自动分析代码、运行测试并应用补丁。
- **📊 深度财务数据审计**: 直接通过 Python 加载 Excel/CSV 原始数据(绕过 RAG生成图表并实时预览。
- **📝 长任务项目管理**: 自动拆解复杂任务并持久化 TOD·O 进度,跨会话跟踪执行状态。
---
## ⭐ 支持与交流 (Support)
如果这个插件对您有所帮助,请在 [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) 项目上点个 **Star** 💫,这是对我最大的鼓励。
---
## 🚀 安装与配置 (Installation)
### 1) 导入函数
1. 打开 OpenWebUI前往 **工作区** -> **函数**
2. 点击 **+** (创建函数),完整粘贴 `github_copilot_sdk.py` 的内容。
3. 点击保存并确保已启用。
### 2) 获取 Token (Get Token)
1. 访问 [GitHub Token 设置](https://github.com/settings/tokens?type=beta)。
2. 创建 **Fine-grained token**,授予 **Account permissions** -> **Copilot Requests** 访问权限。
3. 将生成的 Token 填入插件的 `GH_TOKEN` 配置项中。
### 3) 认证配置要求(必填)
你必须至少配置以下一种凭据:
- `GH_TOKEN`GitHub Copilot 官方订阅路径),或
- `BYOK_API_KEY`OpenAI/Anthropic 自带 Key 路径)。
如果两者都未配置,模型列表将不会出现。
### 4) 配套插件 (强烈推荐)
为了获得最佳的文件处理体验,请安装 [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6)。
---
### 📤 增强型发布工具与交互式组件
`publish_file_from_workspace` 现采用更清晰、可落地的交付规范:
- **Artifacts 模式(`artifacts`,默认)**:返回 `[Preview]` + `[Download]`,并可附带 `html_embed`,在 ```html 代码块中直接渲染。
- **Rich UI 模式(`richui`**:仅返回 `[Preview]` + `[Download]`,由发射器自动触发集成式预览(消息中不输出 iframe 代码块)。
- **📄 PDF 安全交付规则**:仅输出 Markdown 链接(可用时为 `[Preview]` + `[Download]`)。**禁止通过 iframe/html 方式嵌入 PDF。**
- **⚡ 稳定双通道发布**:在本地与对象存储后端下,保持交互预览与持久下载链接一致可用。
- **✅ 状态集成**:通过 OpenWebUI 状态栏实时反馈发布进度与完成状态。
- **📘 发布工具指南GitHub**[publish_file_from_workspace 工具指南(中文)](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/pipes/github-copilot-sdk/PUBLISH_FILE_FROM_WORKSPACE_CN.md)
---
### 🧩 OpenWebUI Skills 桥接与 `manage_skills` 工具
SDK 现在具备与 OpenWebUI **工作区 > Skills** 的双向同步能力:
- **🔄 自动同步**: 每次请求时,前端定义的技能会自动作为 `SKILL.md` 文件夹同步至 SDK 共享缓存Agent 可直接调用。
- **🛠️ `manage_skills` 工具**: 内置专业工具,赋予 Agent (或用户) 绝对的技能管理权。
- `list`: 列出所有已安装技能及描述。
- `install`: 从 GitHub URL (自动转换归档链接) 或直接从 `.zip`/`.tar.gz` 安装。
- `create`: 从当前会话内容创建新技能目录,支持写入 `SKILL.md` 及辅助资源文件 (脚本、模板)。
- `edit`: 更新现有技能文件夹。
- `delete`: 原子化删除本地目录及关联的数据库条目,防止僵尸技能复活。
- **📁 完整的文件夹支持**: 不同于数据库中单文件存储SDK 会加载技能的**整个目录**。这使得技能可以携带二进制脚本、数据文件或复杂模板。
- **🌐 持久化共享缓存**: 技能存储在 `OPENWEBUI_SKILLS_SHARED_DIR/shared/`,跨会话及容器重启持久存在。
- **📚 技能完整文档GitHub**: [manage_skills 工具指南(中文)](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/pipes/github-copilot-sdk/SKILLS_MANAGER_CN.md) | [Skills Best Practices中文](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/pipes/github-copilot-sdk/SKILLS_BEST_PRACTICES_CN.md)
---
## 📋 常见问题与依赖 (Troubleshooting)
- **Agent 无法识别文件?**: 请确保已安装并启用了 Files Filter 插件,否则原始文件会被 RAG 干扰。
- **看不到状态更新或 TODO 进度条?**: 状态气泡会覆盖处理/工具阶段;而 TODO 进度条仅在 Agent 使用 `update_todo` 工具(通常是复杂任务)时出现。
- **依赖安装**: 本管道会自动管理 `github-copilot-sdk` (Python 包) 并优先直接使用内置的二进制 CLI无需手动干预。
---
## 更新日志 (Changelog)
完整历史记录请见 GitHub: [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)
- **工具无法使用?** 请检查是否安装了 `github-copilot-sdk`
- **文件找不到?** 确保已启用配套的 `Files Filter` 插件。
- **BYOK 报错?** 确认 `llm_base_url` 包含协议前缀(如 `https://`)且模型 ID 准确无误
- **卡在 "Thinking..."?** 检查后端网络连接,流式传输可能受某些代理拦截。

View File

@@ -15,7 +15,7 @@ Pipes allow you to:
## Available Pipe Plugins
- [GitHub Copilot SDK](github-copilot-sdk.md) (v0.9.0) - Official GitHub Copilot SDK integration. Features **Workspace Isolation**, **Zero-config OpenWebUI Tool Bridge**, **BYOK** support, and **dynamic MCP discovery**. **NEW in v0.9.0: OpenWebUI Skills Bridge**, reinforced status bar stability, and persistent SDK config management. [View Deep Dive](github-copilot-sdk-deep-dive.md) | [**View Advanced Tutorial**](github-copilot-sdk-tutorial.md) | [**View Detailed Usage Guide**](github-copilot-sdk-usage-guide.md).
- [GitHub Copilot SDK](github-copilot-sdk.md) (v0.9.1) - Official GitHub Copilot SDK integration. Features **Workspace Isolation**, **Zero-config OpenWebUI Tool Bridge**, **BYOK** support, and **dynamic MCP discovery**. **NEW in v0.9.1: MCP filter reliability fix** for `server:mcp:{id}` chat selection and function filter consistency. [View Deep Dive](github-copilot-sdk-deep-dive.md) | [**View Advanced Tutorial**](github-copilot-sdk-tutorial.md) | [**View Detailed Usage Guide**](github-copilot-sdk-usage-guide.md).
- **[Case Study: GitHub 100 Star Growth Analysis](star-prediction-example.md)** - Learn how to use the GitHub Copilot SDK Pipe with Minimax 2.1 to automatically analyze CSV data and generate project growth reports.
- **[Case Study: High-Quality Video to GIF Conversion](video-processing-example.md)** - See how the model uses system-level FFmpeg to accelerate, scale, and optimize colors for screen recordings.

View File

@@ -15,7 +15,7 @@ Pipes 可以用于:
## 可用的 Pipe 插件
- [GitHub Copilot SDK](github-copilot-sdk.zh.md) (v0.9.0) - GitHub Copilot SDK 官方集成。具备**工作区安全隔离**、**零配置工具桥接**与**BYOK (自带 Key) 支持**。**v0.9.0 重量级更新OpenWebUI Skills 桥接**、状态栏稳定性加固,以及持久化 SDK 配置目录管理(`COPILOTSDK_CONFIG_DIR`。[查看深度架构解析](github-copilot-sdk-deep-dive.zh.md) | [**查看进阶实战教程**](github-copilot-sdk-tutorial.zh.md) | [**查看详细使用手册**](github-copilot-sdk-usage-guide.zh.md)。
- [GitHub Copilot SDK](github-copilot-sdk.zh.md) (v0.9.1) - GitHub Copilot SDK 官方集成。具备**工作区安全隔离**、**零配置工具桥接**与**BYOK (自带 Key) 支持**。**v0.9.1 更新MCP 过滤可靠性修复**,修正 `server:mcp:{id}` 聊天选择匹配并提升函数过滤一致性。[查看深度架构解析](github-copilot-sdk-deep-dive.zh.md) | [**查看进阶实战教程**](github-copilot-sdk-tutorial.zh.md) | [**查看详细使用手册**](github-copilot-sdk-usage-guide.zh.md)。
- **[实战案例GitHub 100 Star 增长预测](star-prediction-example.zh.md)** - 展示如何使用 GitHub Copilot SDK Pipe 结合 Minimax 2.1 模型,自动编写脚本分析 CSV 数据并生成详细的项目增长报告。
- **[实战案例:视频高质量 GIF 转换与加速](video-processing-example.zh.md)** - 演示模型如何通过底层 FFmpeg 工具对录屏进行加速、缩放及双阶段色彩优化处理。

View File

@@ -4,4 +4,5 @@ OpenWebUI native Tool plugins that can be used across models.
## Available Tool Plugins
- [OpenWebUI Skills Manager Tool](openwebui-skills-manager-tool.md) (v0.2.0) - Simple native skill management (`list/show/install/create/update/delete`).
- [OpenWebUI Skills Manager Tool](openwebui-skills-manager-tool.md) (v0.2.1) - Simple native skill management (`list/show/install/create/update/delete`).
- [Smart Mind Map Tool](smart-mind-map-tool.md) (v1.0.0) - Intelligently analyzes text content and proactively generates interactive mind maps to help users structure and visualize knowledge.

View File

@@ -4,4 +4,5 @@
## 可用 Tool 插件
- [OpenWebUI Skills 管理工具](openwebui-skills-manager-tool.zh.md) (v0.2.0) - 简化技能管理(`list/show/install/create/update/delete`)。
- [OpenWebUI Skills 管理工具](openwebui-skills-manager-tool.zh.md) (v0.2.1) - 简化技能管理(`list/show/install/create/update/delete`)。
- [智能思维导图工具 (Smart Mind Map Tool)](smart-mind-map-tool.zh.md) (v1.0.0) - 智能分析文本内容并主动生成交互式思维导图,帮助用户结构化与可视化知识。

View File

@@ -1,9 +1,14 @@
# OpenWebUI Skills Manager Tool
**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 0.2.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)
**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 0.2.1 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)
A standalone OpenWebUI Tool plugin for managing native Workspace Skills across models.
## What's New
- Added GitHub skills-directory auto-discovery for `install_skill` (e.g., `.../tree/main/skills`) to install all child skills in one request.
- Fixed language detection with robust frontend-first fallback (`__event_call__` + timeout), request header fallback, and profile fallback.
## Key Features
- Native skill management
@@ -22,6 +27,10 @@ A standalone OpenWebUI Tool plugin for managing native Workspace Skills across m
## Installation
1. Open OpenWebUI → Workspace → Tools
2. Create Tool and paste:
- `plugins/tools/openwebui-skills-manager/openwebui_skills_manager.py`
2. Install **OpenWebUI Skills Manager Tool** from the official marketplace
3. Save and enable for your chat/model
### Manual Installation (Alternative)
- Create Tool and paste:
- `plugins/tools/openwebui-skills-manager/openwebui_skills_manager.py`

View File

@@ -1,9 +1,14 @@
# OpenWebUI Skills 管理工具
**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 0.2.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)
**Author:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **Version:** 0.2.1 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)
一个可跨模型使用的 OpenWebUI 原生 Tool 插件,用于管理 Workspace Skills。
## 最新更新
- `install_skill` 新增 GitHub 技能目录自动发现(例如 `.../tree/main/skills`),可一键安装目录下所有子技能。
- 修复语言获取逻辑:前端优先(`__event_call__` + 超时保护),并回退到请求头与用户资料。
## 核心特性
- 原生技能管理
@@ -22,6 +27,10 @@
## 安装方式
1. 打开 OpenWebUI → Workspace → Tools
2. 新建 Tool 并粘贴:
- `plugins/tools/openwebui-skills-manager/openwebui_skills_manager.py`
2. 在官方市场安装 **OpenWebUI Skills 管理工具**
3. 保存并在模型/聊天中启用
### 手动安装(备选)
- 新建 Tool 并粘贴:
- `plugins/tools/openwebui-skills-manager/openwebui_skills_manager.py`

View File

@@ -0,0 +1,62 @@
# Smart Mind Map Tool - Knowledge Visualization & Structuring
Smart Mind Map Tool is the tool version of the popular Smart Mind Map plugin for OpenWebUI. It allows the model to proactively generate interactive mind maps during conversations by intelligently analyzing context and structuring knowledge into visual hierarchies.
> **Note**: Prefer the manual trigger button instead? Check out the [Smart Mind Map Action Version](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a) here.
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 1.0.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
---
## 🚀 Why is there a Tool version?
1. **Powered by OpenWebUI 0.8.0 Rich UI**: Previous versions of OpenWebUI did not support embedding custom HTML/iframes directly into the chat stream. Starting with 0.8.0, the platform introduced full Rich UI rendering support for **both Actions and Tools**, unleashing interactive frontend possibilities.
2. **AI Autonomous Invocation (vs. Action)**: While an **Action** is passive and requires a manual button click from the user, the **Tool** version gives the model **autonomy**. The AI can analyze the conversational context and decide on its own exactly when generating a mind map would be most helpful, offering a true "smart assistant" experience.
It is perfect for:
- Summarizing complex discussions.
- Planning projects or outlining articles.
- Explaining hierarchical concepts.
## ✨ Key Features
-**Proactive Generation**: The AI triggers the tool automatically when it senses a need for structured visualization.
-**Full Context Awareness**: Supports aggregation of the entire conversation history to generate comprehensive knowledge maps.
-**Native Multi-language UI (i18n)**: Automatically detects and adapts to your browser/system language (en-US, zh-CN, ja-JP, etc.).
-**Premium UI/UX**: Matches the Action version with a compact toolbar, glassmorphism aesthetics, and professional borders.
-**Interactive Controls**: Zoom (In/Out/Reset), Level-based expansion (Default to Level 3), and Fullscreen mode.
-**High-Quality Export**: Export your mind maps as print-ready PNG images.
## 🛠️ Installation & Setup
1. **Install**: Upload `smart_mind_map_tool.py` to your OpenWebUI Admin Settings -> Plugins -> Tools.
2. **Enable Native Tool Calling**: Navigate to `Admin Settings -> Models` or your workspace settings, and ensure that **Native Tool Calling** is enabled for your selected model. This is required for the AI to reliably and actively invoke the tool automatically.
3. **Assign**: Toggle the tool "ON" for your desired models in the workspace or model settings.
4. **Configure**:
- `MESSAGE_COUNT`: Set to `12` (default) to use the 12 most recent messages, or `0` for the entire conversation history.
- `MODEL_ID`: Specify a preferred model for analysis (defaults to the current chat model).
## ⚙️ Configuration (Valves)
| Parameter | Default | Description |
| :--- | :--- | :--- |
| `MODEL_ID` | (Empty) | The model used for text analysis. If empty, uses the current chat model. |
| `MESSAGE_COUNT` | `12` | Number of messages to aggregate. `0` = All messages. |
| `MIN_TEXT_LENGTH` | `100` | Minimum character count required to trigger a mind map. |
## ❓ FAQ & Troubleshooting
- **Language mismatch?**: The tool uses a 4-level detection (Frontend Script > Browser Header > User Profile > Default). Ensure your browser language is set correctly.
- **Too tiny or too large?**: We've optimized the height to `500px` for inline chat display with a responsive "Fit to Screen" logic.
- **Exporting**: Click the "⛶" for fullscreen if you want a wider view before exporting to PNG.
---
## ⭐ Support
If this tool helps you visualize ideas better, please give us a star on [GitHub](https://github.com/Fu-Jie/openwebui-extensions).
## ⚖️ License
MIT License. Developed with ❤️ by Fu-Jie.

View File

@@ -0,0 +1,62 @@
# 思维导图工具 - 知识可视化与结构化利器
思维导图工具Smart Mind Map Tool是广受好评的“思维导图”插件的工具Tool版本。它赋予了模型主动生成交互式思维导图的能力通过智能分析上下文将碎片化知识转化为层级分明的视觉架构。
> **说明**:如果您更倾向于手动点击按钮触发生成,可以获取 [思维导图 Action动作版本](https://openwebui.com/posts/turn_any_text_into_beautiful_mind_maps_3094c59a)。
**作者:** [Fu-Jie](https://github.com/Fu-Jie) | **版本:** 1.0.0 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可证:** MIT
---
## 🚀 为什么会有工具Tool版本
1. **得益于 OpenWebUI 0.8.0 的 Rich UI 特性**:在以前的版本中,是不支持直接将自定义的 HTML/iframe 嵌入到对话流中的。而从 0.8.0 开始平台不仅支持了这种顺滑的前端组件直出Rich UI而且同时对 **Action****Tool** 开放了该能力。
2. **AI 自主调用(区别于 Action****Action** 是被动的,需要用户在输入框或消息旁手动点击触发;而 **Tool** 赋予了模型**自主权**。AI 可以根据对话上下文,自行判断在什么时候为您生成导图最有帮助,实现真正的“智能助理”体验。
它非常适合以下场景:
- 总结复杂的对话内容。
- 规划项目、整理文章大纲。
- 解释具有层级结构的抽象概念。
## ✨ 核心特性
-**主动触发生成**AI 在感知到需要视觉化展示时会自动调用工具生成导图。
-**全量上下文感知**支持聚合整个会话历史MESSAGE_COUNT 为 0生成最完整的知识地图。
-**原生多语言 UI (i18n)**:自动检测并适配浏览器/系统语言(简体中文、繁体中文、英文、日文、韩文等)。
-**统一的高级视觉**:完全复刻 Action 版本的极简工具栏、玻璃拟态审美以及专业边框阴影。
-**深度交互控制**:支持缩放(放大/缩小/重置)、层级调节(默认为 3 级展开)以及全屏模式。
-**高品质导出**:支持将导图导出为超高清 PNG 图片。
## 🛠️ 安装与设置
1. **安装**:在 OpenWebUI 管理员设置 -> 插件 -> 工具中上传 `smart_mind_map_tool.py`
2. **启用原生理机制**:在“管理员设置 -> 模型”或配置里,确保目标模型**启用了原生工具调用Native Tool Calling**。只有开启这个能力AI 才能自主并稳定地触发 Tool 功能。
3. **分配工具**:在工作区或聊天界面处为目标模型选中并挂载本工具。
4. **配置**
- `MESSAGE_COUNT`:设置为 `12`(默认)以使用最近的 12 条对话记录,或设置为 `0` 聚合全部历史。
- `MODEL_ID`:指定分析导图时偏好的模型(留空则默认使用当前模型)。
## ⚙️ 配置参数 (Valves)
| 参数 | 默认值 | 描述 |
| :--- | :--- | :--- |
| `MODEL_ID` | (留空) | 用于文本分析的模型 ID。留空则随当前聊天模型。 |
| `MESSAGE_COUNT` | `12` | 聚合消息的数量。`0` 表示全量消息,`12` 表示截取最近的 12 条。 |
| `MIN_TEXT_LENGTH` | `100` | 触发导图分析所需的最小字符长度。 |
## ❓ 常见问题
- **语言显示不正确?**:工具采用 4 级探测机制(前端脚本 > 浏览器头 > 用户资料 > 默认)。请检查浏览器语言设置。
- **生成的导图太小或太大?**:我们针对对话流内联显示优化了 `500px` 的固定高度,并配有自适应缩放逻辑。
- **导出图片**:建议先点击“⛶”进入全屏,获得最佳构图后再点击导出。
---
## ⭐ 支持
如果这个工具帮您理清了思路,欢迎在 [GitHub](https://github.com/Fu-Jie/openwebui-extensions) 给我们一个 Star。
## ⚖️ 许可证
MIT License. Designed with ❤️ by Fu-Jie.

View File

@@ -1,359 +0,0 @@
#!/usr/bin/env python3
"""
======================================================================
Staged README Synchronizer to OpenWebUI Community
暂存 README 文件同步到 OpenWebUI 社区工具
======================================================================
PURPOSE / 用途:
--------------
This script synchronizes staged README.md/README_CN.md files to their
corresponding OpenWebUI Community posts automatically. It's designed for
batch updating documentation content without modifying plugin versions
or media attachments.
本脚本自动将暂存的 README.md/README_CN.md 文件同步到对应的 OpenWebUI
社区帖子。专为批量更新文档内容设计,不修改插件版本或媒体附件。
USAGE / 使用方法:
----------------
1. Set up environment:
配置环境:
Create a .env file in the repository root with:
在仓库根目录创建 .env 文件,包含:
OPENWEBUI_API_KEY=your_api_key_here
2. Stage README files to sync:
暂存需要同步的 README 文件:
git add plugins/actions/my_plugin/README.md
git add plugins/actions/my_plugin/README_CN.md
3. Run the script:
运行脚本:
python plugins/debug/common_tools/update_readmes_to_market.py
WORKFLOW / 工作流程:
-------------------
1. Load OPENWEBUI_API_KEY from .env file
从 .env 文件加载 OPENWEBUI_API_KEY
2. Get list of staged README.md/README_CN.md files via git
通过 git 获取暂存的 README.md/README_CN.md 文件列表
3. For each staged README:
对于每个暂存的 README
a. Locate the corresponding plugin .py file
定位对应的插件 .py 文件
b. Extract openwebui_id/post_id from plugin frontmatter
从插件前置信息中提取 openwebui_id/post_id
c. Fetch existing post data from OpenWebUI Community API
从 OpenWebUI 社区 API 获取现有帖子数据
d. Update post content with new README content
用新的 README 内容更新帖子内容
e. Push changes via API (preserves version & media)
通过 API 推送更改(保留版本和媒体)
REQUIREMENTS / 依赖要求:
-----------------------
- python-dotenv: For loading .env configuration
用于加载 .env 配置文件
- Git repository: Must be run from a git-tracked workspace
必须在 git 跟踪的工作区中运行
KEY FEATURES / 关键特性:
-----------------------
✅ Only updates content field (不仅更新内容字段)
✅ Skips files without openwebui_id (跳过没有 openwebui_id 的文件)
✅ Automatically matches CN/EN plugin files (自动匹配中英文插件文件)
✅ Supports staged plugin source code updates (支持暂存插件源码更新)
✅ Safe: Won't modify version or media fields (安全:不会修改版本或媒体字段)
NOTES / 注意事项:
---------------
- This is a DEBUG/DEVELOPMENT tool, not for production workflows
这是一个调试/开发工具,不用于生产工作流
- Always verify changes in OpenWebUI Community after sync
同步后务必在 OpenWebUI 社区中验证更改
- Requires valid API key with update permissions
需要具有更新权限的有效 API 密钥
AUTHOR / 作者:
-------------
Fu-Jie
GitHub: https://github.com/Fu-Jie/openwebui-extensions
======================================================================
"""
from __future__ import annotations
import importlib.util
import os
import re
import sys
import subprocess
from pathlib import Path
from typing import Dict, Optional, List
def _load_dotenv(repo_root: Path) -> None:
try:
from dotenv import load_dotenv # type: ignore
except Exception as exc: # pragma: no cover
print("Missing dependency: python-dotenv. Please install it and retry.")
raise SystemExit(1) from exc
env_path = repo_root / ".env"
load_dotenv(env_path)
def _get_repo_root() -> Path:
return Path(__file__).resolve().parents[3]
def _get_staged_readmes(repo_root: Path) -> List[Path]:
try:
output = subprocess.check_output(
[
"git",
"-C",
str(repo_root),
"diff",
"--cached",
"--name-only",
"--",
"*.md",
],
text=True,
)
except subprocess.CalledProcessError as exc:
print(f"Failed to read staged files: {exc}")
return []
paths = []
for line in output.splitlines():
line = line.strip()
if not line:
continue
if line.endswith("README.md") or line.endswith("README_CN.md"):
paths.append(repo_root / line)
return paths
def _get_staged_plugin_files(repo_root: Path) -> List[Path]:
try:
output = subprocess.check_output(
[
"git",
"-C",
str(repo_root),
"diff",
"--cached",
"--name-only",
"--",
"*.py",
],
text=True,
)
except subprocess.CalledProcessError as exc:
print(f"Failed to read staged files: {exc}")
return []
paths = []
for line in output.splitlines():
line = line.strip()
if not line:
continue
if "/plugins/" not in line:
continue
if line.endswith("__init__.py") or os.path.basename(line).startswith("test_"):
continue
paths.append(repo_root / line)
return paths
def _parse_frontmatter(content: str) -> Dict[str, str]:
match = re.search(r'^\s*"""\n(.*?)\n"""', content, re.DOTALL)
if not match:
match = re.search(r'"""\n(.*?)\n"""', content, re.DOTALL)
if not match:
return {}
frontmatter = match.group(1)
meta: Dict[str, str] = {}
for line in frontmatter.split("\n"):
if ":" in line:
key, value = line.split(":", 1)
meta[key.strip()] = value.strip()
return meta
def _find_plugin_file(readme_path: Path) -> Optional[Path]:
plugin_dir = readme_path.parent
is_cn = readme_path.name.lower().endswith("readme_cn.md")
py_files = [
p
for p in plugin_dir.glob("*.py")
if p.name != "__init__.py" and not p.name.startswith("test_")
]
if not py_files:
return None
cn_files = [p for p in py_files if p.stem.endswith("_cn")]
en_files = [p for p in py_files if not p.stem.endswith("_cn")]
candidates = cn_files + en_files if is_cn else en_files + cn_files
# Prefer files that contain openwebui_id/post_id in frontmatter
for candidate in candidates:
post_id = _get_post_id(candidate)
if post_id:
return candidate
return candidates[0] if candidates else None
def _get_post_id(plugin_file: Path) -> Optional[str]:
try:
content = plugin_file.read_text(encoding="utf-8")
except Exception:
return None
meta = _parse_frontmatter(content)
return meta.get("openwebui_id") or meta.get("post_id")
def _get_plugin_metadata(plugin_file: Path) -> Dict[str, str]:
try:
content = plugin_file.read_text(encoding="utf-8")
except Exception:
return {}
return _parse_frontmatter(content)
def _find_readme_for_plugin(plugin_file: Path) -> Optional[str]:
plugin_dir = plugin_file.parent
is_cn = plugin_file.stem.endswith("_cn")
readme_candidates = ["README_CN.md", "README.md"] if is_cn else ["README.md", "README_CN.md"]
for name in readme_candidates:
readme_path = plugin_dir / name
if readme_path.exists():
return readme_path.read_text(encoding="utf-8")
return None
def main() -> int:
repo_root = _get_repo_root()
_load_dotenv(repo_root)
api_key = os.environ.get("OPENWEBUI_API_KEY")
if not api_key:
print("OPENWEBUI_API_KEY is not set in environment.")
return 1
client_module_path = repo_root / "scripts" / "openwebui_community_client.py"
spec = importlib.util.spec_from_file_location(
"openwebui_community_client", client_module_path
)
if not spec or not spec.loader:
print("Failed to load openwebui_community_client module.")
return 1
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
client = module.get_client(api_key)
staged_plugins = _get_staged_plugin_files(repo_root)
staged_readmes = _get_staged_readmes(repo_root)
if not staged_plugins and not staged_readmes:
print("No staged README or plugin files found.")
return 0
updated_post_ids: set[str] = set()
for plugin_file in staged_plugins:
if not plugin_file.exists():
print(f"Skipped (missing): {plugin_file}")
continue
post_id = _get_post_id(plugin_file)
if not post_id:
print(f"Skipped (no openwebui_id): {plugin_file}")
continue
try:
post_data = client.get_post(post_id)
if not post_data:
print(f"Skipped (post not found): {plugin_file}")
continue
source_code = plugin_file.read_text(encoding="utf-8")
metadata = _get_plugin_metadata(plugin_file)
readme_content = _find_readme_for_plugin(plugin_file)
ok = client.update_plugin(
post_id=post_id,
source_code=source_code,
readme_content=readme_content or metadata.get("description", ""),
metadata=metadata,
media_urls=None,
)
if ok:
updated_post_ids.add(post_id)
print(f"Updated plugin -> {plugin_file} (post_id: {post_id})")
except Exception as exc:
print(f"Failed: {plugin_file} ({exc})")
for readme_path in staged_readmes:
if not readme_path.exists():
print(f"Skipped (missing): {readme_path}")
continue
plugin_file = _find_plugin_file(readme_path)
if not plugin_file:
print(f"Skipped (no plugin file): {readme_path}")
continue
post_id = _get_post_id(plugin_file)
if not post_id:
print(f"Skipped (no openwebui_id): {readme_path}")
continue
try:
if post_id in updated_post_ids:
print(f"Skipped (already updated via plugin): {readme_path}")
continue
post_data = client.get_post(post_id)
if not post_data:
print(f"Skipped (post not found): {readme_path}")
continue
readme_content = readme_path.read_text(encoding="utf-8")
# Update README content only, keep other fields unchanged.
post_data["content"] = readme_content
ok = client.update_post(post_id, post_data)
if ok:
print(f"Updated README -> {readme_path} (post_id: {post_id})")
except Exception as exc:
print(f"Failed: {readme_path} ({exc})")
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,138 @@
# Copilot SDK 自动任务脚本使用说明
本目录提供了一个通用任务执行脚本,以及两个示例任务脚本:
- `auto_programming_task.py`(通用)
- `run_mindmap_action_to_tool.sh`示例mind map action → tool
- `run_infographic_action_to_tool.sh`示例infographic action → tool
## 1. 先决条件
- 在仓库根目录执行(非常重要)
- Python 3 可用
- 当前环境中可正常使用 Copilot SDK / CLI
建议先验证:
python3 plugins/debug/copilot-sdk/auto_programming_task.py --help | head -40
---
## 2. 核心行为(当前默认)
`auto_programming_task.py` 默认是 **两阶段自动执行**
1) 先规划PlanningAI 根据你的需求自动补全上下文、扩展为可执行计划。
2) 再执行ExecutionAI 按计划直接改代码并给出结果。
如果你要关闭“先规划”,可使用 `--no-plan-first`
---
## 3. 可复制命令(通用)
### 3.1 最常用:直接写任务文本
python3 plugins/debug/copilot-sdk/auto_programming_task.py \
--task "把 plugins/actions/xxx/xxx.py 转成 plugins/tools/xxx-tool/ 下的单文件 Tool 插件。保留 i18n 和语言回退逻辑。不要升级 SDK 版本。" \
--cwd "$PWD" \
--model "gpt-5.3-codex" \
--reasoning-effort "xhigh" \
--timeout 3600 \
--stream \
--trace-events \
--heartbeat-seconds 8
### 3.2 使用任务文件(长任务推荐)
先写任务文件(例如 task.txt再执行
python3 plugins/debug/copilot-sdk/auto_programming_task.py \
--task-file "./task.txt" \
--cwd "$PWD" \
--model "gpt-5.3-codex" \
--reasoning-effort "xhigh" \
--timeout 3600 \
--stream \
--trace-events \
--heartbeat-seconds 8
### 3.3 关闭规划阶段(仅直接执行)
python3 plugins/debug/copilot-sdk/auto_programming_task.py \
--task "你的任务" \
--cwd "$PWD" \
--model "gpt-5-mini" \
--reasoning-effort "medium" \
--timeout 1800 \
--no-plan-first
---
## 4. 可复制命令(示例脚本)
### 4.1 Mind Map 示例任务
./plugins/debug/copilot-sdk/run_mindmap_action_to_tool.sh
### 4.2 Infographic 示例任务
./plugins/debug/copilot-sdk/run_infographic_action_to_tool.sh
说明:这两个脚本是“固定任务模板”,适合当前仓库;复制到其他仓库时通常需要改任务内容。
---
## 5. 结果如何判定“完成”
建议同时满足以下条件:
1) 进程退出码为 0
2) 输出中出现阶段结束信息(含最终摘要)
3) 看到 `session.idle`(若是 `session.error` 则未完成)
4) `git diff --name-only` 显示改动范围符合你的约束
可复制检查命令:
echo $?
git diff --name-only
git status --short
---
## 6. 参数速查
- `--task`:直接传任务文本
- `--task-file`:从文件读取任务文本(与 `--task` 二选一)
- `--cwd`:工作区目录(建议用 `$PWD`
- `--model`:模型(例如 `gpt-5.3-codex``gpt-5-mini`
- `--reasoning-effort``low|medium|high|xhigh`
- `--timeout`:超时秒数
- `--stream`:实时输出增量内容
- `--trace-events`:输出事件流,便于排错
- `--heartbeat-seconds`:心跳输出间隔
- `--no-plan-first`:关闭默认“先规划后执行”
---
## 7. 常见问题
### Q1为什么提示找不到脚本
你大概率不在仓库根目录。先执行:
pwd
确认后再运行命令。
### Q2执行很久没有输出
加上 `--trace-events --stream`,并适当增大 `--timeout`
### Q3改动超出预期范围
把范围约束明确写进任务文本,例如:
“不要修改其他文件代码,可以读取整个项目作为代码库。”
并在完成后用:
git diff --name-only
进行核对。

View File

@@ -0,0 +1,447 @@
#!/usr/bin/env python3
"""
Run an autonomous programming task via Copilot SDK.
Usage:
python plugins/debug/copilot-sdk/auto_programming_task.py \
--task "Fix failing tests in tests/test_xxx.py" \
--cwd /Users/fujie/app/python/oui/openwebui-extensions
Notes:
- Default model is gpt-5-mini (low-cost for repeated runs).
- This script DOES NOT pin/upgrade SDK versions.
- Copilot CLI must be available (or set COPILOT_CLI_PATH).
"""
import argparse
import asyncio
import os
import sys
import textwrap
from pathlib import Path
from typing import Optional
DEFAULT_TASK = (
"Convert plugins/actions/smart-mind-map/smart_mind_map.py (Action plugin) "
"into a Tool plugin implementation under plugins/tools/. "
"Keep Copilot SDK version unchanged, follow patterns from "
"plugins/pipes/github-copilot-sdk/, and implement a runnable MVP with "
"i18n/status events/basic validation."
)
def _ensure_copilot_importable() -> None:
"""Try local SDK path fallback if `copilot` package is not installed."""
try:
import copilot # noqa: F401
return
except Exception:
pass
candidates = []
env_path = os.environ.get("COPILOT_SDK_PYTHON_PATH", "").strip()
if env_path:
candidates.append(Path(env_path))
# Default sibling repo path: ../copilot-sdk/python
# Current file: plugins/debug/copilot-sdk/auto_programming_task.py
repo_root = Path(__file__).resolve().parents[3]
candidates.append(repo_root.parent / "copilot-sdk" / "python")
for path in candidates:
if path.exists():
sys.path.insert(0, str(path))
try:
import copilot # noqa: F401
return
except Exception:
continue
raise RuntimeError(
"Cannot import `copilot` package. Install copilot-sdk python package "
"or set COPILOT_SDK_PYTHON_PATH to copilot-sdk/python directory."
)
def _build_agent_prompt(task: str, cwd: str, extra_context: Optional[str]) -> str:
extra = extra_context.strip() if extra_context else ""
return textwrap.dedent(
f"""
You are an autonomous coding agent running in workspace: {cwd}
Primary task:
{task}
Requirements:
1. Inspect relevant files and implement changes directly in the workspace.
2. Keep changes minimal and focused on the task.
3. If tests/build commands exist, run targeted validation for changed scope.
4. If blocked, explain the blocker and propose concrete next steps.
5. At the end, provide a concise summary of:
- files changed
- what was implemented
- validation results
{f'Additional context:\n{extra}' if extra else ''}
"""
).strip()
def _build_planning_prompt(task: str, cwd: str, extra_context: Optional[str]) -> str:
extra = extra_context.strip() if extra_context else ""
return textwrap.dedent(
f"""
You are a senior autonomous coding planner running in workspace: {cwd}
User requirement (may be underspecified):
{task}
Goal:
Expand the requirement into an actionable implementation plan that can be executed end-to-end without extra clarification whenever possible.
Output format (strict):
1) Expanded Objective (clear, concrete, scoped)
2) Assumptions (only necessary assumptions)
3) Step-by-step Plan (ordered, verifiable)
4) Validation Plan (how to verify changes)
5) Execution Brief (concise instruction for implementation agent)
Constraints:
- Keep scope minimal and aligned with the user requirement.
- Do not invent unrelated features.
- Prefer practical MVP completion.
{f'Additional context:\n{extra}' if extra else ''}
"""
).strip()
def _build_execution_prompt(
task: str, cwd: str, extra_context: Optional[str], plan_text: str
) -> str:
extra = extra_context.strip() if extra_context else ""
return textwrap.dedent(
f"""
You are an autonomous coding agent running in workspace: {cwd}
User requirement:
{task}
Planner output (must follow):
{plan_text}
Execution requirements:
1. Execute the plan directly; do not stop after analysis.
2. If the original requirement is underspecified, use the planner assumptions and continue.
3. Keep changes minimal, focused, and runnable.
4. Run targeted validation for changed scope where possible.
5. If blocked by missing prerequisites, report blocker and the smallest next action.
6. Finish with concise summary:
- files changed
- implemented behavior
- validation results
{f'Additional context:\n{extra}' if extra else ''}
"""
).strip()
async def _run_single_session(
client,
args: argparse.Namespace,
prompt: str,
stage_name: str,
stream_output: bool,
) -> tuple[int, str]:
from copilot.types import PermissionHandler
def _auto_user_input_handler(request, _invocation):
question = ""
if isinstance(request, dict):
question = str(request.get("question", "")).lower()
choices = request.get("choices") or []
if choices and isinstance(choices, list):
preferred = args.auto_user_answer.strip()
for choice in choices:
c = str(choice)
if preferred and preferred.lower() == c.lower():
return {"answer": c, "wasFreeform": False}
return {"answer": str(choices[0]), "wasFreeform": False}
preferred = args.auto_user_answer.strip() or "continue"
if "confirm" in question or "proceed" in question:
preferred = "yes"
return {"answer": preferred, "wasFreeform": True}
session_config = {
"model": args.model,
"reasoning_effort": args.reasoning_effort,
"streaming": True,
"infinite_sessions": {
"enabled": True,
},
"on_permission_request": PermissionHandler.approve_all,
"on_user_input_request": _auto_user_input_handler,
}
session = await client.create_session(session_config)
done = asyncio.Event()
full_messages = []
has_error = False
def on_event(event):
nonlocal has_error
etype = getattr(event, "type", "unknown")
if hasattr(etype, "value"):
etype = etype.value
if args.trace_events:
print(f"\n[{stage_name}][EVENT] {etype}", flush=True)
if etype == "assistant.message_delta" and stream_output:
delta = getattr(event.data, "delta_content", "") or ""
if delta:
print(delta, end="", flush=True)
elif etype == "assistant.message":
content = getattr(event.data, "content", "") or ""
if content:
full_messages.append(content)
elif etype == "session.error":
has_error = True
done.set()
elif etype == "session.idle":
done.set()
unsubscribe = session.on(on_event)
heartbeat_task = None
async def _heartbeat():
while not done.is_set():
await asyncio.sleep(max(3, int(args.heartbeat_seconds)))
if not done.is_set():
print(
f"[{stage_name}][heartbeat] waiting for assistant events...",
flush=True,
)
try:
heartbeat_task = asyncio.create_task(_heartbeat())
await session.send({"prompt": prompt, "mode": "immediate"})
await asyncio.wait_for(done.wait(), timeout=args.timeout)
if stream_output:
print("\n")
final_message = full_messages[-1] if full_messages else ""
if final_message:
print(f"\n===== {stage_name} FINAL MESSAGE =====\n")
print(final_message)
if has_error:
return 1, final_message
return 0, final_message
except asyncio.TimeoutError:
print(f"\n❌ [{stage_name}] Timeout after {args.timeout}s")
return 124, ""
except Exception as exc:
print(f"\n❌ [{stage_name}] Run failed: {exc}")
return 1, ""
finally:
if heartbeat_task:
heartbeat_task.cancel()
try:
unsubscribe()
except Exception:
pass
try:
await session.destroy()
except Exception:
pass
async def run_task(args: argparse.Namespace) -> int:
_ensure_copilot_importable()
from copilot import CopilotClient
task_text = (args.task or "").strip()
if args.task_file:
task_text = Path(args.task_file).read_text(encoding="utf-8").strip()
if not task_text:
task_text = DEFAULT_TASK
direct_prompt = _build_agent_prompt(task_text, args.cwd, args.extra_context)
client_options = {
"cwd": args.cwd,
"log_level": args.log_level,
}
if args.cli_path:
client_options["cli_path"] = args.cli_path
if args.github_token:
client_options["github_token"] = args.github_token
print(f"🚀 Starting Copilot SDK task runner")
print(f" cwd: {args.cwd}")
print(f" model: {args.model}")
print(f" reasoning_effort: {args.reasoning_effort}")
print(f" plan_first: {args.plan_first}")
client = CopilotClient(client_options)
await client.start()
try:
if args.plan_first:
planning_prompt = _build_planning_prompt(
task_text, args.cwd, args.extra_context
)
print("\n🧭 Stage 1/2: Planning and requirement expansion")
plan_code, plan_text = await _run_single_session(
client=client,
args=args,
prompt=planning_prompt,
stage_name="PLANNING",
stream_output=False,
)
if plan_code != 0:
return plan_code
execution_prompt = _build_execution_prompt(
task=task_text,
cwd=args.cwd,
extra_context=args.extra_context,
plan_text=plan_text or "(No planner output provided)",
)
print("\n⚙️ Stage 2/2: Execute plan autonomously")
exec_code, _ = await _run_single_session(
client=client,
args=args,
prompt=execution_prompt,
stage_name="EXECUTION",
stream_output=args.stream,
)
return exec_code
print("\n⚙️ Direct mode: Execute task without planning stage")
exec_code, _ = await _run_single_session(
client=client,
args=args,
prompt=direct_prompt,
stage_name="EXECUTION",
stream_output=args.stream,
)
return exec_code
finally:
try:
await client.stop()
except Exception:
pass
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Run one autonomous programming task with Copilot SDK"
)
parser.add_argument(
"--task",
default="",
help="Task description text (if empty, uses built-in default task)",
)
parser.add_argument("--task-file", default="", help="Path to a task text file")
parser.add_argument("--cwd", default=os.getcwd(), help="Workspace directory")
parser.add_argument(
"--model",
default="gpt-5-mini",
help="Model id for Copilot session (default: gpt-5-mini)",
)
parser.add_argument(
"--reasoning-effort",
default="medium",
choices=["low", "medium", "high", "xhigh"],
help="Reasoning effort",
)
parser.add_argument("--timeout", type=int, default=1800, help="Timeout seconds")
parser.add_argument(
"--log-level",
default="info",
choices=["trace", "debug", "info", "warn", "error"],
help="Copilot client log level",
)
parser.add_argument(
"--github-token",
default=os.environ.get("GH_TOKEN", ""),
help="Optional GitHub token; defaults to GH_TOKEN",
)
parser.add_argument(
"--cli-path",
default=os.environ.get("COPILOT_CLI_PATH", ""),
help="Optional Copilot CLI path",
)
parser.add_argument(
"--extra-context",
default="",
help="Optional extra context appended to the task prompt",
)
parser.add_argument(
"--stream",
action="store_true",
help="Print assistant delta stream in real-time",
)
parser.add_argument(
"--trace-events",
action="store_true",
help="Print each SDK event type for debugging",
)
parser.add_argument(
"--auto-user-answer",
default="continue",
help="Default answer for on_user_input_request",
)
parser.add_argument(
"--heartbeat-seconds",
type=int,
default=12,
help="Heartbeat interval while waiting for events",
)
parser.add_argument(
"--plan-first",
action="store_true",
help="Run planning stage before execution (default behavior)",
)
parser.add_argument(
"--no-plan-first",
action="store_true",
help="Disable planning stage and run direct execution",
)
return parser
def main() -> int:
parser = build_parser()
args = parser.parse_args()
if args.task_file and (args.task or "").strip():
parser.error("Use either --task or --task-file, not both")
args.plan_first = True
if args.no_plan_first:
args.plan_first = False
elif args.plan_first:
args.plan_first = True
return asyncio.run(run_task(args))
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,137 @@
#!/usr/bin/env bash
# run_owui_api_docs_phases.sh
# One-click runner: generate OpenWebUI API documentation across 8 phases.
#
# Usage:
# ./plugins/debug/copilot-sdk/run_owui_api_docs_phases.sh
# ./plugins/debug/copilot-sdk/run_owui_api_docs_phases.sh --start-phase 3
# ./plugins/debug/copilot-sdk/run_owui_api_docs_phases.sh --only-phase 1
#
# Working directory: /Users/fujie/app/python/oui/open-webui (open-webui source)
# Task files: plugins/debug/copilot-sdk/tasks/owui-api-docs/phases/
set -euo pipefail
# ── Resolve paths ────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" # openwebui-extensions root
TASKS_DIR="${SCRIPT_DIR}/tasks/owui-api-docs/phases"
TARGET_CWD="/Users/fujie/app/python/oui/open-webui" # source repo to scan
RUNNER="${SCRIPT_DIR}/auto_programming_task.py"
PYTHON="${PYTHON:-python3}"
# ── Arguments ────────────────────────────────────────────────────────────────
START_PHASE=1
ONLY_PHASE=""
while [[ $# -gt 0 ]]; do
case "$1" in
--start-phase)
START_PHASE="$2"; shift 2 ;;
--only-phase)
ONLY_PHASE="$2"; shift 2 ;;
*)
echo "Unknown argument: $1" >&2; exit 1 ;;
esac
done
# ── Phase definitions ─────────────────────────────────────────────────────────
declare -a PHASE_FILES=(
"01_route_index.txt"
"02_auth_users_groups_models.txt"
"03_chats_channels_memories_notes.txt"
"04_files_folders_knowledge_retrieval.txt"
"05_ollama_openai_audio_images.txt"
"06_tools_functions_pipelines_skills_tasks.txt"
"07_configs_prompts_evaluations_analytics_scim_utils.txt"
"08_consolidation_index.txt"
)
declare -a PHASE_LABELS=(
"Route Index (master table)"
"Auth / Users / Groups / Models"
"Chats / Channels / Memories / Notes"
"Files / Folders / Knowledge / Retrieval"
"Ollama / OpenAI / Audio / Images"
"Tools / Functions / Pipelines / Skills / Tasks"
"Configs / Prompts / Evaluations / Analytics / SCIM / Utils"
"Consolidation — README + JSON"
)
# ── Pre-flight checks ─────────────────────────────────────────────────────────
echo "============================================================"
echo " OpenWebUI API Docs — Phase Runner"
echo "============================================================"
echo " Source (--cwd): ${TARGET_CWD}"
echo " Task files: ${TASKS_DIR}"
echo " Runner: ${RUNNER}"
echo ""
if [[ ! -d "${TARGET_CWD}" ]]; then
echo "ERROR: Target source directory not found: ${TARGET_CWD}" >&2
exit 1
fi
if [[ ! -f "${RUNNER}" ]]; then
echo "ERROR: Runner script not found: ${RUNNER}" >&2
exit 1
fi
# ── Run phases ────────────────────────────────────────────────────────────────
TOTAL=${#PHASE_FILES[@]}
PASSED=0
FAILED=0
for i in "${!PHASE_FILES[@]}"; do
PHASE_NUM=$((i + 1))
TASK_FILE="${TASKS_DIR}/${PHASE_FILES[$i]}"
LABEL="${PHASE_LABELS[$i]}"
# --only-phase filter
if [[ -n "${ONLY_PHASE}" && "${PHASE_NUM}" != "${ONLY_PHASE}" ]]; then
echo " [SKIP] Phase ${PHASE_NUM}: ${LABEL}"
continue
fi
# --start-phase filter
if [[ "${PHASE_NUM}" -lt "${START_PHASE}" ]]; then
echo " [SKIP] Phase ${PHASE_NUM}: ${LABEL} (before start phase)"
continue
fi
if [[ ! -f "${TASK_FILE}" ]]; then
echo " [ERROR] Task file not found: ${TASK_FILE}" >&2
FAILED=$((FAILED + 1))
break
fi
echo ""
echo "──────────────────────────────────────────────────────────"
echo " Phase ${PHASE_NUM}/${TOTAL}: ${LABEL}"
echo " Task file: ${PHASE_FILES[$i]}"
echo "──────────────────────────────────────────────────────────"
if "${PYTHON}" "${RUNNER}" \
--task-file "${TASK_FILE}" \
--cwd "${TARGET_CWD}" \
--model "claude-sonnet-4.6" \
--reasoning-effort high \
--no-plan-first; then
echo " ✓ Phase ${PHASE_NUM} completed successfully."
PASSED=$((PASSED + 1))
else
EXIT_CODE=$?
echo ""
echo " ✗ Phase ${PHASE_NUM} FAILED (exit code: ${EXIT_CODE})." >&2
echo " Fix the issue and re-run with: --start-phase ${PHASE_NUM}" >&2
FAILED=$((FAILED + 1))
exit "${EXIT_CODE}"
fi
done
# ── Summary ──────────────────────────────────────────────────────────────────
echo ""
echo "============================================================"
echo " Run complete: ${PASSED} passed, ${FAILED} failed"
echo " Output: ${TARGET_CWD}/api_docs/"
echo "============================================================"

View File

@@ -0,0 +1,74 @@
# OpenWebUI API Documentation — Phase Run Order
## Overview
This task set reads the OpenWebUI backend source code and generates a complete
API reference in `api_docs/` inside the open-webui repository.
**Source repo:** `/Users/fujie/app/python/oui/open-webui`
**Output directory:** `/Users/fujie/app/python/oui/open-webui/api_docs/`
**Task files dir:** `plugins/debug/copilot-sdk/tasks/owui-api-docs/phases/`
---
## Phase Execution Order
Run phases sequentially. Each phase depends on the previous.
| Order | Task File | Coverage | ~Lines Read |
|-------|-----------|----------|-------------|
| 1 | `01_route_index.txt` | main.py + all 26 router files → master route table | ~15,000 |
| 2 | `02_auth_users_groups_models.txt` | auths, users, groups, models | ~4,600 |
| 3 | `03_chats_channels_memories_notes.txt` | chats, channels, memories, notes | ~5,500 |
| 4 | `04_files_folders_knowledge_retrieval.txt` | files, folders, knowledge, retrieval | ~5,200 |
| 5 | `05_ollama_openai_audio_images.txt` | ollama, openai, audio, images | ~6,900 |
| 6 | `06_tools_functions_pipelines_skills_tasks.txt` | tools, functions, pipelines, skills, tasks | ~3,200 |
| 7 | `07_configs_prompts_evaluations_analytics_scim_utils.txt` | configs, prompts, evaluations, analytics, scim, utils | ~3,400 |
| 8 | `08_consolidation_index.txt` | Consolidates all outputs → README.md + JSON | (reads generated files) |
---
## Output Files (after all phases complete)
```
open-webui/api_docs/
├── README.md ← Master index + quick reference
├── 00_route_index.md ← Complete route table (200+ endpoints)
├── 02_auths.md
├── 02_users.md
├── 02_groups.md
├── 02_models.md
├── 03_chats.md
├── 03_channels.md
├── 03_memories.md
├── 03_notes.md
├── 04_files.md
├── 04_folders.md
├── 04_knowledge.md
├── 04_retrieval.md
├── 05_ollama.md
├── 05_openai.md
├── 05_audio.md
├── 05_images.md
├── 06_tools.md
├── 06_functions.md
├── 06_pipelines.md
├── 06_skills.md
├── 06_tasks.md
├── 07_configs.md
├── 07_prompts.md
├── 07_evaluations.md
├── 07_analytics.md
├── 07_scim.md
├── 07_utils.md
└── openwebui_api.json ← Machine-readable summary (all routes)
```
---
## Notes
- Each phase uses `--no-plan-first` (detailed instructions already provided).
- Working directory for all phases: `/Users/fujie/app/python/oui/open-webui`
- The one-click runner: `run_owui_api_docs_phases.sh`
- If a phase fails, fix the issue and re-run that single phase before continuing.

View File

@@ -0,0 +1,41 @@
Phase 1 Mission:
Scan the entire OpenWebUI backend source and produce a master route index table.
Source root: backend/open_webui/
Target output directory: api_docs/
Constraints:
- Read-only on ALL files EXCEPT under api_docs/ (create it if missing).
- Do NOT generate per-endpoint detail yet — only the master table.
- Cover every router file in backend/open_webui/routers/.
- Also read backend/open_webui/main.py to capture route prefixes (app.include_router calls).
Deliverables:
1) Create directory: api_docs/
2) Create file: api_docs/00_route_index.md
Content of 00_route_index.md must contain:
- A table with columns: Module | HTTP Method | Path | Handler Function | Auth Required | Brief Description
- One row per route decorator found in every router file.
- "Auth Required" = YES if the route depends on get_verified_user / get_admin_user / similar dependency, NO otherwise.
- "Brief Description" = first sentence of the handler's docstring, or empty string if none.
- Group rows by Module (router file name without .py).
- At the top: a summary section listing total_route_count and module_count.
Process:
1. Read main.py — extract all app.include_router() calls, note prefix and tags per router.
2. For each router file in backend/open_webui/routers/, read it fully.
3. Find every @router.get/@router.post/@router.put/@router.delete/@router.patch decorator.
4. For each decorator: record path, method, function name, auth dependency, docstring.
5. Write the combined table to api_docs/00_route_index.md.
Exit Criteria:
- api_docs/00_route_index.md exists.
- Table contains at least 100 rows (the codebase has 200+ routes).
- No placeholder or TBD.
- Total route count printed at the top.
Final output format:
- List of files created/updated.
- Total routes found.
- Any router files that could not be parsed and why.

View File

@@ -0,0 +1,82 @@
Phase 2 Mission:
Generate detailed API reference documentation for authentication, users, groups, and models endpoints.
Prerequisites:
- api_docs/00_route_index.md must already exist (from Phase 1).
Source files to read (fully):
- backend/open_webui/routers/auths.py
- backend/open_webui/routers/users.py
- backend/open_webui/routers/groups.py
- backend/open_webui/routers/models.py
- backend/open_webui/models/auths.py (Pydantic models)
- backend/open_webui/models/users.py
- backend/open_webui/models/groups.py (if exists)
- backend/open_webui/models/models.py
Output files to create under api_docs/:
- 02_auths.md
- 02_users.md
- 02_groups.md
- 02_models.md
Per-endpoint format (use this EXACTLY for every endpoint in each file):
---
### {HTTP_METHOD} {full_path}
**Summary:** One sentence description.
**Auth:** Admin only | Verified user | Public
**Request**
| Location | Field | Type | Required | Description |
|----------|-------|------|----------|-------------|
| Header | Authorization | Bearer token | Yes | JWT token |
| Body | field_name | type | Yes/No | description |
| Query | param_name | type | No | description |
| Path | param_name | type | Yes | description |
*If no request body/params, write: "No additional parameters."*
**Response `200`**
```json
{
"example_field": "example_value"
}
```
| Field | Type | Description |
|-------|------|-------------|
| field_name | type | description |
**Error Responses**
| Status | Meaning |
|--------|---------|
| 400 | Bad request / validation error |
| 401 | Not authenticated |
| 403 | Insufficient permissions |
| 404 | Resource not found |
---
Instructions:
1. Read each router file fully to understand every route.
2. Trace Pydantic model definitions from the corresponding models/ file.
3. Fill in every field from actual code — no guessing.
4. If a field is Optional with a default, mark Required = No.
5. For auth: check FastAPI dependency injection (Depends(get_verified_user) → "Verified user", Depends(get_admin_user) → "Admin only").
6. List ALL endpoints in the router — do not skip any.
Exit Criteria:
- 4 output files created.
- Every route from 00_route_index.md for these modules is covered.
- No placeholder or TBD.
Final output format:
- List of files created.
- Count of endpoints documented per file.

View File

@@ -0,0 +1,87 @@
Phase 3 Mission:
Generate detailed API reference documentation for chat, channels, memories, and notes endpoints.
Prerequisites:
- api_docs/00_route_index.md must already exist (from Phase 1).
Source files to read (fully):
- backend/open_webui/routers/chats.py
- backend/open_webui/routers/channels.py
- backend/open_webui/routers/memories.py
- backend/open_webui/routers/notes.py
- backend/open_webui/models/chats.py (Pydantic models)
- backend/open_webui/models/channels.py
- backend/open_webui/models/memories.py
- backend/open_webui/models/notes.py (if exists)
- backend/open_webui/models/messages.py (shared message models)
Output files to create under api_docs/:
- 03_chats.md
- 03_channels.md
- 03_memories.md
- 03_notes.md
Per-endpoint format:
---
### {HTTP_METHOD} {full_path}
**Summary:** One sentence description.
**Auth:** Admin only | Verified user | Public
**Request**
| Location | Field | Type | Required | Description |
|----------|-------|------|----------|-------------|
| Body | field_name | type | Yes/No | description |
*If no parameters, write: "No additional parameters."*
**Response `200`**
```json
{
"example_field": "example_value"
}
```
| Field | Type | Description |
|-------|------|-------------|
| field_name | type | description |
**Error Responses**
| Status | Meaning |
|--------|---------|
| 401 | Not authenticated |
| 403 | Insufficient permissions |
| 404 | Resource not found |
---
Special notes for this phase:
- chats.py is 1527 lines with ~40 routes — document ALL of them.
- channels.py is 2133 lines — document ALL routes; note WebSocket upgrade endpoints separately.
- For WebSocket endpoints: note the protocol (ws://) and describe events/message payload format.
- Pay special attention to chat history structure: messages array, history.messages dict.
- Note pagination parameters (skip, limit, page) where applicable.
Instructions:
1. Read each router file fully.
2. Trace Pydantic model definitions from the corresponding models/ file.
3. For complex response types (list of chats, paginated results), show the wrapper structure.
4. If a route modifies chat history, document the exact history object shape.
5. List ALL endpoints — do not skip paginated variants.
Exit Criteria:
- 4 output files created.
- Every route from 00_route_index.md for these modules is covered.
- WebSocket endpoints documented with payload shape.
- No placeholder or TBD.
Final output format:
- List of files created.
- Count of endpoints documented per file.
- Note any complex schemas that required deep tracing.

View File

@@ -0,0 +1,94 @@
Phase 4 Mission:
Generate detailed API reference documentation for files, folders, knowledge base, and retrieval endpoints.
Prerequisites:
- api_docs/00_route_index.md must already exist (from Phase 1).
Source files to read (fully):
- backend/open_webui/routers/files.py (~911 lines)
- backend/open_webui/routers/folders.py (~351 lines)
- backend/open_webui/routers/knowledge.py (~1139 lines)
- backend/open_webui/routers/retrieval.py (~2820 lines — LARGEST FILE)
- backend/open_webui/models/files.py
- backend/open_webui/models/folders.py
- backend/open_webui/models/knowledge.py
Output files to create under api_docs/:
- 04_files.md
- 04_folders.md
- 04_knowledge.md
- 04_retrieval.md
Per-endpoint format:
---
### {HTTP_METHOD} {full_path}
**Summary:** One sentence description.
**Auth:** Admin only | Verified user | Public
**Request**
| Location | Field | Type | Required | Description |
|----------|-------|------|----------|-------------|
| Body | field_name | type | Yes/No | description |
*If no parameters, write: "No additional parameters."*
**Response `200`**
```json
{
"example_field": "example_value"
}
```
| Field | Type | Description |
|-------|------|-------------|
| field_name | type | description |
**Error Responses**
| Status | Meaning |
|--------|---------|
| 401 | Not authenticated |
| 404 | Resource not found |
---
Special notes for this phase:
FILES:
- File upload uses multipart/form-data — document the form fields.
- File metadata response: id, filename, meta.content_type, size, user_id.
- File content endpoint: returns raw bytes — note Content-Type header behavior.
KNOWLEDGE:
- Knowledge base endpoints interact with vector store — note which ones trigger embedding/indexing.
- Document the "files" array in knowledge base objects (which file IDs are linked).
- Add/remove files from knowledge base: document the exact request shape.
RETRIEVAL:
- retrieval.py is 2820 lines; it configures the RAG pipeline (embedding models, chunk settings, etc.).
- Prioritize documenting: query endpoint, config GET/POST endpoints, embedding model endpoints.
- For config endpoints: document ALL configuration fields (chunk_size, chunk_overlap, top_k, etc.).
- Document the "process" endpoints (process_doc, process_web, process_youtube) with their request shapes.
Instructions:
1. Read ALL source files listed above.
2. For retrieval.py: focus on public API surface (router endpoints), not internal helper functions.
3. Document file upload endpoints with multipart form fields clearly marked.
4. Trace vector DB config models in retrieval.py to document all configurable fields.
Exit Criteria:
- 4 output files created.
- retrieval.py endpoints fully documented including all config fields.
- File upload endpoints show form-data field names.
- No placeholder or TBD.
Final output format:
- List of files created.
- Count of endpoints documented per file.
- Note any tricky schemas (nested config objects, etc.).

View File

@@ -0,0 +1,98 @@
Phase 5 Mission:
Generate detailed API reference documentation for AI provider endpoints: Ollama, OpenAI-compatible, Audio, and Images.
Prerequisites:
- api_docs/00_route_index.md must already exist (from Phase 1).
Source files to read (fully):
- backend/open_webui/routers/ollama.py (~1884 lines)
- backend/open_webui/routers/openai.py (~1466 lines)
- backend/open_webui/routers/audio.py (~1397 lines)
- backend/open_webui/routers/images.py (~1164 lines)
Output files to create under api_docs/:
- 05_ollama.md
- 05_openai.md
- 05_audio.md
- 05_images.md
Per-endpoint format:
---
### {HTTP_METHOD} {full_path}
**Summary:** One sentence description.
**Auth:** Admin only | Verified user | Public
**Request**
| Location | Field | Type | Required | Description |
|----------|-------|------|----------|-------------|
| Body | field_name | type | Yes/No | description |
**Response `200`**
```json
{
"example_field": "example_value"
}
```
| Field | Type | Description |
|-------|------|-------------|
| field_name | type | description |
**Streaming:** Yes / No *(add this line for endpoints that support SSE/streaming)*
**Error Responses**
| Status | Meaning |
|--------|---------|
| 401 | Not authenticated |
| 503 | Upstream provider unavailable |
---
Special notes for this phase:
OLLAMA:
- Endpoints are mostly pass-through proxies to Ollama's own API.
- Document which endpoints are admin-only (model management) vs user-accessible (generate/chat).
- For streaming endpoints (generate, chat), note: "Supports SSE streaming via stream=true."
- Document the model pull/push/delete management endpoints carefully.
OPENAI:
- Endpoints proxy to configured OpenAI-compatible backend.
- Document the /api/openai/models endpoint (returns merged model list).
- Note which endpoints pass through request body to upstream unchanged.
- Document admin endpoints for adding/removing OpenAI API connections.
AUDIO:
- Document: transcription (STT), TTS synthesis, and audio config endpoints.
- For file upload endpoints: specify multipart/form-data field names.
- Document supported audio formats and any size limits visible in code.
- Note: Engine types (openai, whisper, etc.) and configuration endpoints.
IMAGES:
- Document: image generation endpoints and image engine config.
- Note DALL-E vs ComfyUI vs Automatic1111 backend differences if documented in code.
- Document image config GET/POST: size, steps, model, and other parameters.
Instructions:
1. Read each file fully — they are complex proxying routers.
2. For pass-through proxy routes: still document the expected request/response shape.
3. Distinguish between admin configuration routes and user-facing generation routes.
4. Streaming endpoints must be clearly marked with "Streaming: Yes" and note the SSE event format.
Exit Criteria:
- 4 output files created.
- Every route from 00_route_index.md for these modules is covered.
- Streaming endpoints clearly annotated.
- No placeholder or TBD.
Final output format:
- List of files created.
- Count of endpoints documented per file.
- Note streaming endpoints count per module.

View File

@@ -0,0 +1,103 @@
Phase 6 Mission:
Generate detailed API reference documentation for tools, functions, pipelines, skills, and tasks endpoints.
Prerequisites:
- docs/open_webui_api/00_route_index.md must already exist (from Phase 1).
- NOTE: Output directory is api_docs/ (not docs/open_webui_api/).
Source files to read (fully):
- backend/open_webui/routers/tools.py (~868 lines)
- backend/open_webui/routers/functions.py (~605 lines)
- backend/open_webui/routers/pipelines.py (~540 lines)
- backend/open_webui/routers/skills.py (~447 lines)
- backend/open_webui/routers/tasks.py (~764 lines)
- backend/open_webui/models/tools.py
- backend/open_webui/models/functions.py
- backend/open_webui/models/skills.py
Output files to create under api_docs/:
- 06_tools.md
- 06_functions.md
- 06_pipelines.md
- 06_skills.md
- 06_tasks.md
Per-endpoint format:
---
### {HTTP_METHOD} {full_path}
**Summary:** One sentence description.
**Auth:** Admin only | Verified user | Public
**Request**
| Location | Field | Type | Required | Description |
|----------|-------|------|----------|-------------|
| Body | field_name | type | Yes/No | description |
**Response `200`**
```json
{
"example_field": "example_value"
}
```
| Field | Type | Description |
|-------|------|-------------|
| field_name | type | description |
**Error Responses**
| Status | Meaning |
|--------|---------|
| 401 | Not authenticated |
| 404 | Resource not found |
---
Special notes for this phase:
TOOLS:
- Tools are user-created Python functions exposed to LLM. Document CRUD operations.
- The tool "specs" field: document its structure (list of OpenAI function call specs).
- Document the "export" endpoint if present.
FUNCTIONS:
- Functions include filters, actions, pipes registered by admin.
- Document the `type` field values: "filter", "action", "pipe".
- Document the `meta` and `valves` fields structure.
PIPELINES:
- Pipelines connect to external pipeline servers.
- Document: add pipeline (URL + API key), list pipelines, get valves, set valves.
- Note: pipelines proxy through to an external server; document that behavior.
SKILLS:
- Skills are agent-style plugins with multi-step execution.
- Document the skills schema: name, content (Python source), meta.
- Note if there's a "call" endpoint for executing a skill.
TASKS:
- Tasks module handles background processing (title generation, tag generation, etc.).
- Document config endpoints (GET/POST for task-specific LLM settings).
- Document any direct invocation endpoints.
Instructions:
1. Read all source files fully.
2. For valves/specs/meta fields with complex structure, show the full nested schema.
3. Distinguish admin-only CRUD from user-accessible execution endpoints.
4. For endpoints that execute code (tools, functions, skills), clearly note security implications.
Exit Criteria:
- 5 output files created.
- Every route from 00_route_index.md for these modules is covered.
- Complex nested schemas (valves, specs, meta) fully documented.
- No placeholder or TBD.
Final output format:
- List of files created.
- Count of endpoints documented per file.

View File

@@ -0,0 +1,109 @@
Phase 7 Mission:
Generate detailed API reference documentation for configuration, prompts, evaluations, analytics, SCIM, and utility endpoints.
Prerequisites:
- api_docs/00_route_index.md must already exist (from Phase 1).
Source files to read (fully):
- backend/open_webui/routers/configs.py (~548 lines)
- backend/open_webui/routers/prompts.py (~759 lines)
- backend/open_webui/routers/evaluations.py (~466 lines)
- backend/open_webui/routers/analytics.py (~454 lines)
- backend/open_webui/routers/scim.py (~1030 lines)
- backend/open_webui/routers/utils.py (~123 lines)
- backend/open_webui/models/prompts.py
- backend/open_webui/config.py (for config field definitions)
Output files to create under api_docs/:
- 07_configs.md
- 07_prompts.md
- 07_evaluations.md
- 07_analytics.md
- 07_scim.md
- 07_utils.md
Per-endpoint format:
---
### {HTTP_METHOD} {full_path}
**Summary:** One sentence description.
**Auth:** Admin only | Verified user | Public
**Request**
| Location | Field | Type | Required | Description |
|----------|-------|------|----------|-------------|
| Body | field_name | type | Yes/No | description |
**Response `200`**
```json
{
"example_field": "example_value"
}
```
| Field | Type | Description |
|-------|------|-------------|
| field_name | type | description |
**Error Responses**
| Status | Meaning |
|--------|---------|
| 401 | Not authenticated |
| 404 | Resource not found |
---
Special notes for this phase:
CONFIGS:
- This is the most important module in this phase.
- The global config GET/POST endpoints control system-wide settings.
- Read backend/open_webui/config.py to enumerate ALL configurable fields.
- Document every config field with its type, default, and effect.
- Group config fields by category (auth, RAG, models, UI, etc.) in the output.
PROMPTS:
- System prompts stored by users.
- Document CRUD operations and the command field (trigger word like "/summarize").
- Note the "access_control" field structure.
EVALUATIONS:
- Feedback/rating data for model responses.
- Document the feedback object structure (rating, comment, model_id, etc.).
- Note any aggregation/analytics endpoints.
ANALYTICS:
- Usage statistics endpoints.
- Document what metrics are tracked and aggregation options.
SCIM:
- SCIM 2.0 protocol for enterprise user/group provisioning.
- Document: /Users, /Groups, /ServiceProviderConfig, /ResourceTypes endpoints.
- Note: SCIM uses different Content-Type and auth mechanism — document these.
- Follow SCIM 2.0 RFC schema format for user/group objects.
UTILS:
- Miscellaneous utility endpoints.
- Document all available utilities (markdown renderer, code executor, etc.).
Instructions:
1. Read config.py in addition to router files to get complete field lists.
2. For SCIM: follow SCIM 2.0 RFC conventions in documentation format.
3. For configs: produce a separate "All Config Fields" appendix table.
Exit Criteria:
- 6 output files created.
- configs.md includes appendix table of ALL config fields with defaults.
- scim.md follows SCIM 2.0 documentation conventions.
- No placeholder or TBD.
Final output format:
- List of files created.
- Count of endpoints documented per file.
- Count of config fields documented in configs.md appendix.

View File

@@ -0,0 +1,89 @@
Phase 8 Mission:
Consolidate all previously generated phase outputs into a polished master index and a machine-readable summary.
Prerequisites:
- ALL phase 1~7 output files must exist under api_docs/.
- Specifically, these files must exist:
- api_docs/00_route_index.md
- api_docs/02_auths.md, 02_users.md, 02_groups.md, 02_models.md
- api_docs/03_chats.md, 03_channels.md, 03_memories.md, 03_notes.md
- api_docs/04_files.md, 04_folders.md, 04_knowledge.md, 04_retrieval.md
- api_docs/05_ollama.md, 05_openai.md, 05_audio.md, 05_images.md
- api_docs/06_tools.md, 06_functions.md, 06_pipelines.md, 06_skills.md, 06_tasks.md
- api_docs/07_configs.md, 07_prompts.md, 07_evaluations.md, 07_analytics.md, 07_scim.md, 07_utils.md
Output files to create/update under api_docs/:
1. api_docs/README.md — human-readable master index
2. api_docs/openwebui_api.json — machine-readable OpenAPI-style JSON summary
Content of README.md:
- Title: "OpenWebUI Backend API Reference"
- Subtitle: "Auto-generated from source code. Do not edit manually."
- Generation date (today's date)
- Table of Contents (links to every .md file above)
- Statistics:
- Total module count
- Total route count (from 00_route_index.md)
- Admin-only route count
- Public route count
- Streaming endpoint count
- Quick Reference: a condensed table of the 20 most commonly used endpoints (chat creation, message send, file upload, model list, auth login/logout, etc.)
- Authentication Guide section:
- How to get a JWT token (reference auths.md)
- How to include it in requests (Authorization: Bearer <token>)
- Token expiry behavior
- Common Patterns section:
- Pagination (skip/limit parameters)
- Error response shape: {detail: string}
- Rate limiting (if documented in code)
Content of openwebui_api.json:
A JSON object with this structure:
{
"meta": {
"generated_date": "YYYY-MM-DD",
"source": "backend/open_webui/routers/",
"total_routes": <number>,
"modules": [<list of module names>]
},
"routes": [
{
"module": "auths",
"method": "POST",
"path": "/api/v1/auths/signin",
"handler": "signin",
"auth_required": false,
"auth_type": "public",
"summary": "Authenticate user and return JWT token.",
"request_body": {
"email": {"type": "str", "required": true},
"password": {"type": "str", "required": true}
},
"response_200": {
"token": {"type": "str"},
"token_type": {"type": "str"},
"id": {"type": "str"}
},
"streaming": false
}
]
}
- Include ALL routes from all modules.
- For streaming endpoints: "streaming": true.
Instructions:
1. Read ALL generated phase output files (00 through 07).
2. Parse or summarize endpoint data from each file to populate the JSON.
3. Write README.md with complete statistics and quick reference.
4. Validate: total_routes in README.md must match count in openwebui_api.json.
Exit Criteria:
- api_docs/README.md exists with statistics and ToC.
- api_docs/openwebui_api.json exists with all routes (valid JSON).
- Route counts in README.md and JSON are consistent.
- No placeholder or TBD.
Final output format:
- Confirmation of files created.
- Total routes count in JSON.
- Any modules with missing or incomplete data (for manual review).

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,359 +0,0 @@
"""
title: UI Language Debugger
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,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiPgogIDxwYXRoIGQ9Im01IDggNiA2Ii8+CiAgPHBhdGggZD0ibTQgMTQgNi02IDItMiIvPgogIDxwYXRoIGQ9Ik0yIDVoMTIiLz4KICA8cGF0aCBkPSJNNyAyaDEiLz4KICA8cGF0aCBkPSJtMjIgMjItNS0xMC01IDEwIi8+CiAgPHBhdGggZD0iTTE0IDE4aDYiLz4KPC9zdmc+Cg==
description: Debug UI language detection in the browser console and on-page panel.
"""
import json
import logging
from typing import Optional, Dict, Any, Callable, Awaitable
from pydantic import BaseModel, Field
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger(__name__)
HTML_WRAPPER_TEMPLATE = """
<!-- OPENWEBUI_PLUGIN_OUTPUT -->
<!DOCTYPE html>
<html lang="{user_language}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
padding: 10px;
background-color: transparent;
}
#main-container {
display: flex;
flex-direction: column;
gap: 16px;
width: 100%;
max-width: 100%;
}
/* STYLES_INSERTION_POINT */
</style>
</head>
<body>
<div id="main-container">
<!-- CONTENT_INSERTION_POINT -->
</div>
<!-- SCRIPTS_INSERTION_POINT -->
</body>
</html>
"""
CONTENT_TEMPLATE = """
<div class="lang-debug-card" id="lang-debug-card-{unique_id}">
<div class="lang-debug-header">
🧭 UI Language Debugger
</div>
<div class="lang-debug-body">
<div class="lang-debug-row"><span>python.ui_language</span><code id="lang-py-{unique_id}">{python_language}</code></div>
<div class="lang-debug-row"><span>document.documentElement.lang</span><code id="lang-html-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>document.documentElement.getAttribute('lang')</span><code id="lang-attr-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>document.documentElement.dir</span><code id="lang-dir-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>document.body.lang</span><code id="lang-body-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>navigator.language</span><code id="lang-nav-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>navigator.languages</span><code id="lang-navs-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>localStorage.language</span><code id="lang-store-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>localStorage.locale</span><code id="lang-locale-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>localStorage.i18n</span><code id="lang-i18n-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>localStorage.settings</span><code id="lang-settings-{unique_id}">-</code></div>
<div class="lang-debug-row"><span>document.documentElement.dataset</span><code id="lang-dataset-{unique_id}">-</code></div>
</div>
</div>
"""
STYLE_TEMPLATE = """
.lang-debug-card {
border: 1px solid #e2e8f0;
border-radius: 12px;
background: #ffffff;
overflow: hidden;
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.06);
}
.lang-debug-header {
padding: 12px 16px;
background: linear-gradient(135deg, #6366f1, #8b5cf6);
color: #fff;
font-weight: 600;
}
.lang-debug-body {
padding: 12px 16px;
display: flex;
flex-direction: column;
gap: 8px;
}
.lang-debug-row {
display: flex;
justify-content: space-between;
gap: 12px;
font-size: 0.9em;
color: #1f2937;
}
.lang-debug-row code {
background: #f8fafc;
border: 1px solid #e2e8f0;
padding: 2px 6px;
border-radius: 6px;
color: #0f172a;
}
"""
SCRIPT_TEMPLATE = """
<script>
(function() {{
const uniqueId = "{unique_id}";
const get = (id) => document.getElementById(id + '-' + uniqueId);
const safe = (value) => {
if (value === undefined || value === null || value === "") return "-";
if (Array.isArray(value)) return value.join(", ");
if (typeof value === "object") return JSON.stringify(value);
return String(value);
};
const safeJson = (value) => {
try {
return value ? JSON.stringify(JSON.parse(value)) : "-";
} catch (e) {
return value ? String(value) : "-";
}
};
const settingsRaw = localStorage.getItem('settings');
const i18nRaw = localStorage.getItem('i18n');
const localeRaw = localStorage.getItem('locale');
const payload = {{
htmlLang: document.documentElement.lang,
htmlAttr: document.documentElement.getAttribute('lang'),
htmlDir: document.documentElement.dir,
bodyLang: document.body ? document.body.lang : "",
navigatorLanguage: navigator.language,
navigatorLanguages: navigator.languages,
localStorageLanguage: localStorage.getItem('language'),
localStorageLocale: localeRaw,
localStorageI18n: i18nRaw,
localStorageSettings: settingsRaw,
htmlDataset: document.documentElement.dataset,
}};
get('lang-html').textContent = safe(payload.htmlLang);
get('lang-attr').textContent = safe(payload.htmlAttr);
get('lang-dir').textContent = safe(payload.htmlDir);
get('lang-body').textContent = safe(payload.bodyLang);
get('lang-nav').textContent = safe(payload.navigatorLanguage);
get('lang-navs').textContent = safe(payload.navigatorLanguages);
get('lang-store').textContent = safe(payload.localStorageLanguage);
get('lang-locale').textContent = safe(payload.localStorageLocale);
get('lang-i18n').textContent = safeJson(payload.localStorageI18n);
get('lang-settings').textContent = safeJson(payload.localStorageSettings);
get('lang-dataset').textContent = safe(payload.htmlDataset);
console.group('🧭 UI Language Debugger');
console.log(payload);
console.groupEnd();
}})();
</script>
"""
class Action:
class Valves(BaseModel):
SHOW_STATUS: bool = Field(
default=True,
description="Whether to show operation status updates.",
)
SHOW_DEBUG_LOG: bool = Field(
default=True,
description="Whether to print debug logs in the browser console.",
)
def __init__(self):
self.valves = self.Valves()
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 = {}
return {
"user_id": user_data.get("id", "unknown_user"),
"user_name": user_data.get("name", "User"),
"user_language": user_data.get("language", ""),
}
def _get_chat_context(
self, body: dict, __metadata__: Optional[dict] = None
) -> Dict[str, str]:
chat_id = ""
message_id = ""
if isinstance(body, dict):
chat_id = body.get("chat_id", "")
message_id = body.get("id", "")
if not chat_id or not message_id:
body_metadata = body.get("metadata", {})
if isinstance(body_metadata, dict):
if not chat_id:
chat_id = body_metadata.get("chat_id", "")
if not message_id:
message_id = body_metadata.get("message_id", "")
if __metadata__ and isinstance(__metadata__, dict):
if not chat_id:
chat_id = __metadata__.get("chat_id", "")
if not message_id:
message_id = __metadata__.get("message_id", "")
return {
"chat_id": str(chat_id).strip(),
"message_id": str(message_id).strip(),
}
async def _emit_status(
self,
emitter: Optional[Callable[[Any], Awaitable[None]]],
description: str,
done: bool = False,
):
if self.valves.SHOW_STATUS and emitter:
await emitter(
{"type": "status", "data": {"description": description, "done": done}}
)
async def _emit_debug_log(
self,
emitter: Optional[Callable[[Any], Awaitable[None]]],
title: str,
data: dict,
):
if not self.valves.SHOW_DEBUG_LOG or not emitter:
return
try:
js_code = f"""
(async function() {{
console.group("🛠️ {title}");
console.log({json.dumps(data, ensure_ascii=False)});
console.groupEnd();
}})();
"""
await emitter({"type": "execute", "data": {"code": js_code}})
except Exception as e:
logger.error("Error emitting debug log: %s", e, exc_info=True)
def _merge_html(
self,
existing_html: str,
new_content: str,
new_styles: str = "",
new_scripts: str = "",
user_language: str = "en-US",
) -> str:
if not existing_html:
base_html = HTML_WRAPPER_TEMPLATE.replace("{user_language}", user_language)
else:
base_html = existing_html
if "<!-- CONTENT_INSERTION_POINT -->" in base_html:
base_html = base_html.replace(
"<!-- CONTENT_INSERTION_POINT -->",
f"{new_content}\n <!-- CONTENT_INSERTION_POINT -->",
)
if new_styles and "/* STYLES_INSERTION_POINT */" in base_html:
base_html = base_html.replace(
"/* STYLES_INSERTION_POINT */",
f"{new_styles}\n /* STYLES_INSERTION_POINT */",
)
if new_scripts and "<!-- SCRIPTS_INSERTION_POINT -->" in base_html:
base_html = base_html.replace(
"<!-- SCRIPTS_INSERTION_POINT -->",
f"{new_scripts}\n <!-- SCRIPTS_INSERTION_POINT -->",
)
return base_html
async def action(
self,
body: dict,
__user__: Optional[Dict[str, Any]] = None,
__event_emitter__: Optional[Any] = None,
__event_call__: Optional[Callable[[Any], Awaitable[None]]] = None,
__metadata__: Optional[dict] = None,
__request__: Optional[Any] = None,
) -> Optional[dict]:
await self._emit_status(__event_emitter__, "Detecting UI language...", False)
user_ctx = self._get_user_context(__user__)
await self._emit_debug_log(
__event_emitter__,
"Language Debugger: user context",
user_ctx,
)
ui_language = ""
if __event_call__:
try:
response = await __event_call__(
{
"type": "execute",
"data": {
"code": "return (localStorage.getItem('locale') || localStorage.getItem('language') || (navigator.languages && navigator.languages[0]) || navigator.language || document.documentElement.lang || '')",
},
}
)
await self._emit_debug_log(
__event_emitter__,
"Language Debugger: execute response",
{"response": response},
)
if isinstance(response, dict) and "value" in response:
ui_language = response.get("value", "") or ""
elif isinstance(response, str):
ui_language = response
except Exception as e:
logger.error(
"Failed to read UI language from frontend: %s", e, exc_info=True
)
unique_id = f"lang_{int(__import__('time').time() * 1000)}"
content_html = CONTENT_TEMPLATE.replace("{unique_id}", unique_id).replace(
"{python_language}", ui_language or "-"
)
script_html = SCRIPT_TEMPLATE.replace("{unique_id}", unique_id)
script_html = script_html.replace("{{", "{").replace("}}", "}")
final_html = self._merge_html(
"",
content_html,
STYLE_TEMPLATE,
script_html,
"en",
)
html_embed_tag = f"```html\n{final_html}\n```"
body["messages"][-1]["content"] = (
body["messages"][-1].get("content", "") + "\n\n" + html_embed_tag
)
await self._emit_status(__event_emitter__, "UI language captured.", True)
return body

View File

@@ -1,568 +0,0 @@
# GitHub Copilot SDK 自定义工具快速入门
## 🎯 目标
在 OpenWebUI Pipe 中直接使用 GitHub Copilot SDK 的自定义工具功能,无需集成 OpenWebUI Function 系统。
---
## 📖 基础概念
### Copilot SDK Tool 的三要素
```python
from copilot.types import Tool, ToolInvocation, ToolResult
# 1. Tool Definition工具定义
tool = Tool(
name="tool_name", # 工具名称
description="What it does", # 描述(给 AI 看的)
parameters={...}, # JSON Schema 参数定义
handler=handler_function # 处理函数
)
# 2. Tool Handler处理函数
async def handler_function(invocation: ToolInvocation) -> ToolResult:
# invocation 包含:
# - session_id: 会话 ID
# - tool_call_id: 调用 ID
# - tool_name: 工具名称
# - arguments: dict实际参数
result = do_something(invocation["arguments"])
return ToolResult(
textResultForLlm="结果文本",
resultType="success", # 或 "failure"
error=None,
toolTelemetry={}
)
# 3. Session Configuration会话配置
session_config = SessionConfig(
model="claude-sonnet-4.5",
tools=[tool1, tool2, tool3], # ✅ 传入工具列表
streaming=True
)
```
---
## 💻 完整实现示例
### 示例 1获取当前时间
```python
from datetime import datetime
from copilot.types import Tool, ToolInvocation, ToolResult
def create_time_tool():
"""创建获取时间的工具"""
async def get_time_handler(invocation: ToolInvocation) -> ToolResult:
"""工具处理函数"""
try:
# 获取参数
timezone = invocation["arguments"].get("timezone", "UTC")
format_str = invocation["arguments"].get("format", "%Y-%m-%d %H:%M:%S")
# 执行逻辑
current_time = datetime.now().strftime(format_str)
result_text = f"Current time: {current_time}"
# 返回结果
return ToolResult(
textResultForLlm=result_text,
resultType="success",
error=None,
toolTelemetry={"execution_time": "fast"}
)
except Exception as e:
return ToolResult(
textResultForLlm=f"Error getting time: {str(e)}",
resultType="failure",
error=str(e),
toolTelemetry={}
)
# 创建工具定义
return Tool(
name="get_current_time",
description="Get the current date and time. Useful when user asks 'what time is it' or needs to know the current date.",
parameters={
"type": "object",
"properties": {
"timezone": {
"type": "string",
"description": "Timezone name (e.g., 'UTC', 'Asia/Shanghai')",
"default": "UTC"
},
"format": {
"type": "string",
"description": "Time format string",
"default": "%Y-%m-%d %H:%M:%S"
}
}
},
handler=get_time_handler
)
```
### 示例 2数学计算器
```python
def create_calculator_tool():
"""创建计算器工具"""
async def calculate_handler(invocation: ToolInvocation) -> ToolResult:
try:
expression = invocation["arguments"].get("expression", "")
# 安全检查
allowed_chars = set("0123456789+-*/()., ")
if not all(c in allowed_chars for c in expression):
raise ValueError("Expression contains invalid characters")
# 计算(安全的 eval
result = eval(expression, {"__builtins__": {}})
return ToolResult(
textResultForLlm=f"The result of {expression} is {result}",
resultType="success",
error=None,
toolTelemetry={}
)
except Exception as e:
return ToolResult(
textResultForLlm=f"Calculation error: {str(e)}",
resultType="failure",
error=str(e),
toolTelemetry={}
)
return Tool(
name="calculate",
description="Perform mathematical calculations. Supports basic arithmetic operations (+, -, *, /).",
parameters={
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Mathematical expression to evaluate (e.g., '2 + 2 * 3')"
}
},
"required": ["expression"]
},
handler=calculate_handler
)
```
### 示例 3随机数生成器
```python
import random
def create_random_number_tool():
"""创建随机数生成工具"""
async def random_handler(invocation: ToolInvocation) -> ToolResult:
try:
min_val = invocation["arguments"].get("min", 1)
max_val = invocation["arguments"].get("max", 100)
if min_val >= max_val:
raise ValueError("min must be less than max")
number = random.randint(min_val, max_val)
return ToolResult(
textResultForLlm=f"Generated random number: {number}",
resultType="success",
error=None,
toolTelemetry={}
)
except Exception as e:
return ToolResult(
textResultForLlm=f"Error: {str(e)}",
resultType="failure",
error=str(e),
toolTelemetry={}
)
return Tool(
name="generate_random_number",
description="Generate a random integer within a specified range.",
parameters={
"type": "object",
"properties": {
"min": {
"type": "integer",
"description": "Minimum value (inclusive)",
"default": 1
},
"max": {
"type": "integer",
"description": "Maximum value (inclusive)",
"default": 100
}
}
},
handler=random_handler
)
```
---
## 🔧 集成到 Pipe
### 完整的 Pipe 实现
```python
class Pipe:
class Valves(BaseModel):
# ... 现有 Valves ...
ENABLE_TOOLS: bool = Field(
default=False,
description="Enable custom tools (time, calculator, random)"
)
AVAILABLE_TOOLS: str = Field(
default="all",
description="Available tools: 'all' or comma-separated list (e.g., 'get_current_time,calculate')"
)
def __init__(self):
# ... 现有初始化 ...
self._custom_tools = []
def _initialize_custom_tools(self):
"""初始化自定义工具"""
if not self.valves.ENABLE_TOOLS:
return []
# 定义所有可用工具
all_tools = {
"get_current_time": create_time_tool(),
"calculate": create_calculator_tool(),
"generate_random_number": create_random_number_tool(),
}
# 根据配置过滤工具
if self.valves.AVAILABLE_TOOLS == "all":
return list(all_tools.values())
# 只启用指定的工具
enabled = [t.strip() for t in self.valves.AVAILABLE_TOOLS.split(",")]
return [all_tools[name] for name in enabled if name in all_tools]
async def pipe(
self,
body: dict,
__metadata__: Optional[dict] = None,
__event_emitter__=None,
__event_call__=None,
) -> Union[str, AsyncGenerator]:
# ... 现有代码 ...
# ✅ 初始化工具
custom_tools = self._initialize_custom_tools()
if custom_tools:
await self._emit_debug_log(
f"Enabled {len(custom_tools)} custom tools: {[t.name for t in custom_tools]}",
__event_call__
)
# ✅ 创建会话配置(传入工具)
from copilot.types import SessionConfig, InfiniteSessionConfig
session_config = SessionConfig(
session_id=chat_id if chat_id else None,
model=real_model_id,
streaming=body.get("stream", False),
tools=custom_tools, # ✅✅✅ 关键:传入工具列表
infinite_sessions=infinite_session_config if self.valves.INFINITE_SESSION else None,
)
session = await client.create_session(config=session_config)
# ... 其余代码保持不变 ...
```
---
## 📊 处理工具调用事件
### 在 stream_response 中显示工具调用
```python
async def stream_response(
self, client, session, send_payload, init_message: str = "", __event_call__=None
) -> AsyncGenerator:
# ... 现有代码 ...
def handler(event):
event_type = str(getattr(event.type, "value", event.type))
# ✅ 工具调用开始
if "tool_invocation_started" in event_type or "tool_call_started" in event_type:
tool_name = get_event_data(event, "tool_name", "")
if tool_name:
queue.put_nowait(f"\n\n🔧 **Calling tool**: `{tool_name}`\n")
# ✅ 工具调用完成
elif "tool_invocation_completed" in event_type or "tool_call_completed" in event_type:
tool_name = get_event_data(event, "tool_name", "")
result = get_event_data(event, "result", "")
if tool_name:
queue.put_nowait(f"\n✅ **Tool `{tool_name}` completed**\n")
# ✅ 工具调用失败
elif "tool_invocation_failed" in event_type or "tool_call_failed" in event_type:
tool_name = get_event_data(event, "tool_name", "")
error = get_event_data(event, "error", "")
if tool_name:
queue.put_nowait(f"\n❌ **Tool `{tool_name}` failed**: {error}\n")
# ... 其他事件处理 ...
# ... 其余代码 ...
```
---
## 🧪 测试示例
### 测试 1询问时间
```
User: "What time is it now?"
Expected Flow:
1. Copilot 识别需要调用 get_current_time 工具
2. 调用工具(无参数或默认参数)
3. 工具返回: "Current time: 2026-01-26 15:30:00"
4. Copilot 回答: "The current time is 2026-01-26 15:30:00"
Pipe Output:
---
🔧 **Calling tool**: `get_current_time`
✅ **Tool `get_current_time` completed**
The current time is 2026-01-26 15:30:00
---
```
### 测试 2数学计算
```
User: "Calculate 123 * 456"
Expected Flow:
1. Copilot 调用 calculate 工具
2. 参数: {"expression": "123 * 456"}
3. 工具返回: "The result of 123 * 456 is 56088"
4. Copilot 回答: "123 multiplied by 456 equals 56,088"
Pipe Output:
---
🔧 **Calling tool**: `calculate`
✅ **Tool `calculate` completed**
123 multiplied by 456 equals 56,088
---
```
### 测试 3生成随机数
```
User: "Give me a random number between 1 and 10"
Expected Flow:
1. Copilot 调用 generate_random_number 工具
2. 参数: {"min": 1, "max": 10}
3. 工具返回: "Generated random number: 7"
4. Copilot 回答: "I generated a random number for you: 7"
```
---
## 🔍 调试技巧
### 1. 记录所有工具事件
```python
def handler(event):
event_type = str(getattr(event.type, "value", event.type))
# 记录所有包含 "tool" 的事件
if "tool" in event_type.lower():
event_data = {}
if hasattr(event, "data"):
try:
event_data = {
"type": event_type,
"data": str(event.data)[:200] # 截断长数据
}
except:
pass
self._emit_debug_log_sync(
f"Tool Event: {json.dumps(event_data)}",
__event_call__
)
```
### 2. 验证工具注册
```python
async def pipe(...):
# ...
custom_tools = self._initialize_custom_tools()
# 调试:打印工具信息
if self.valves.DEBUG:
tool_info = [
{
"name": t.name,
"description": t.description[:50],
"has_handler": t.handler is not None
}
for t in custom_tools
]
await self._emit_debug_log(
f"Registered tools: {json.dumps(tool_info, indent=2)}",
__event_call__
)
```
### 3. 测试工具处理函数
```python
# 单独测试工具
async def test_tool():
tool = create_time_tool()
# 模拟调用
invocation = {
"session_id": "test",
"tool_call_id": "test_call",
"tool_name": "get_current_time",
"arguments": {"format": "%H:%M:%S"}
}
result = await tool.handler(invocation)
print(f"Result: {result}")
```
---
## ⚠️ 注意事项
### 1. 工具描述的重要性
工具的 `description` 字段非常重要,它告诉 AI 何时应该使用这个工具:
```python
# ❌ 差的描述
description="Get time"
# ✅ 好的描述
description="Get the current date and time. Use this when the user asks 'what time is it', 'what's the date', or needs to know the current timestamp."
```
### 2. 参数定义
使用标准的 JSON Schema 定义参数:
```python
parameters={
"type": "object",
"properties": {
"param_name": {
"type": "string", # string, integer, boolean, array, object
"description": "Clear description",
"enum": ["option1", "option2"], # 可选:枚举值
"default": "default_value" # 可选:默认值
}
},
"required": ["param_name"] # 必需参数
}
```
### 3. 错误处理
总是捕获异常并返回有意义的错误:
```python
try:
result = do_something()
return ToolResult(
textResultForLlm=f"Success: {result}",
resultType="success",
error=None,
toolTelemetry={}
)
except Exception as e:
return ToolResult(
textResultForLlm=f"Error occurred: {str(e)}",
resultType="failure",
error=str(e), # 用于调试
toolTelemetry={}
)
```
### 4. 异步 vs 同步
工具处理函数可以是同步或异步:
```python
# 同步工具
def sync_handler(invocation):
result = calculate(invocation["arguments"])
return ToolResult(...)
# 异步工具(推荐)
async def async_handler(invocation):
result = await fetch_data(invocation["arguments"])
return ToolResult(...)
```
---
## 🚀 快速开始清单
- [ ] 1. 在 Valves 中添加 `ENABLE_TOOLS` 配置
- [ ] 2. 定义 2-3 个简单的工具函数
- [ ] 3. 实现 `_initialize_custom_tools()` 方法
- [ ] 4. 修改 `SessionConfig` 传入 `tools` 参数
- [ ] 5. 在 `stream_response` 中添加工具事件处理
- [ ] 6. 测试:询问时间、计算数学、生成随机数
- [ ] 7. 添加调试日志
- [ ] 8. 同步中文版本
---
## 📚 完整的工具事件列表
根据 SDK 源码,可能的工具相关事件:
- `tool_invocation_started` / `tool_call_started`
- `tool_invocation_completed` / `tool_call_completed`
- `tool_invocation_failed` / `tool_call_failed`
- `tool_parameter_validation_failed`
实际事件名称可能因 SDK 版本而异,建议先记录所有事件类型:
```python
def handler(event):
print(f"Event type: {event.type}")
```
---
**快速实现入口:** 从示例 1获取时间开始这是最简单的工具可以快速验证整个流程
**作者:** Fu-Jie
**日期:** 2026-01-26

View File

@@ -1,480 +0,0 @@
# OpenWebUI Native Tool Call Display Implementation Guide
**Date:** 2026-01-27
**Purpose:** Analyze and implement OpenWebUI's native tool call display mechanism
---
## 📸 Current vs Native Display
### Current Implementation
```markdown
> 🔧 **Running Tool**: `search_chats`
> ✅ **Tool Completed**: {...}
```
### OpenWebUI Native Display (from screenshot)
- ✅ Collapsible panel: "查看来自 search_chats 的结果"
- ✅ Formatted JSON display
- ✅ Syntax highlighting
- ✅ Expand/collapse functionality
- ✅ Clean visual separation
---
## 🔍 Understanding OpenWebUI's Tool Call Format
### Standard OpenAI Tool Call Message Format
OpenWebUI follows the OpenAI Chat Completion API format for tool calls:
#### 1. Assistant Message with Tool Calls
```python
{
"role": "assistant",
"content": None, # or explanatory text
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "search_chats",
"arguments": '{"query": ""}'
}
}
]
}
```
#### 2. Tool Response Message
```python
{
"role": "tool",
"tool_call_id": "call_abc123",
"name": "search_chats", # Optional but recommended
"content": '{"count": 5, "results": [...]}' # JSON string
}
```
---
## 🎯 Implementation Strategy for Native Display
### Option 1: Event Emitter Approach (Recommended)
Use OpenWebUI's event emitter to send structured tool call data:
```python
async def stream_response(self, ...):
# When tool execution starts
if event_type == "tool.execution_start":
await self._emit_tool_call_start(
emitter=__event_call__,
tool_call_id=tool_call_id,
tool_name=tool_name,
arguments=arguments
)
# When tool execution completes
elif event_type == "tool.execution_complete":
await self._emit_tool_call_result(
emitter=__event_call__,
tool_call_id=tool_call_id,
tool_name=tool_name,
result=result_content
)
```
#### Helper Methods
```python
async def _emit_tool_call_start(
self,
emitter: Optional[Callable[[Any], Awaitable[None]]],
tool_call_id: str,
tool_name: str,
arguments: dict
):
"""Emit a tool call start event to OpenWebUI."""
if not emitter:
return
try:
# OpenWebUI expects tool_calls in assistant message format
await emitter({
"type": "message",
"data": {
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": tool_call_id,
"type": "function",
"function": {
"name": tool_name,
"arguments": json.dumps(arguments, ensure_ascii=False)
}
}
]
}
})
except Exception as e:
logger.error(f"Failed to emit tool call start: {e}")
async def _emit_tool_call_result(
self,
emitter: Optional[Callable[[Any], Awaitable[None]]],
tool_call_id: str,
tool_name: str,
result: Any
):
"""Emit a tool call result to OpenWebUI."""
if not emitter:
return
try:
# Format result as JSON string
if isinstance(result, str):
result_content = result
else:
result_content = json.dumps(result, ensure_ascii=False, indent=2)
# OpenWebUI expects tool results in tool message format
await emitter({
"type": "message",
"data": {
"role": "tool",
"tool_call_id": tool_call_id,
"name": tool_name,
"content": result_content
}
})
except Exception as e:
logger.error(f"Failed to emit tool result: {e}")
```
### Option 2: Message History Injection
Modify the conversation history to include tool calls:
```python
# After tool execution, append to messages
messages.append({
"role": "assistant",
"content": None,
"tool_calls": [{
"id": tool_call_id,
"type": "function",
"function": {
"name": tool_name,
"arguments": json.dumps(arguments)
}
}]
})
messages.append({
"role": "tool",
"tool_call_id": tool_call_id,
"name": tool_name,
"content": json.dumps(result)
})
```
---
## ⚠️ Challenges with Current Architecture
### 1. Streaming Context
Our current implementation uses:
- **Queue-based streaming**: Events → Queue → Yield chunks
- **Text chunks only**: We yield plain text, not structured messages
OpenWebUI's native display requires:
- **Structured message events**: Not text chunks
- **Message-level control**: Need to emit complete messages
### 2. Event Emitter Compatibility
**Current usage:**
```python
# We use event_emitter for status/notifications
await event_emitter({
"type": "status",
"data": {"description": "Processing..."}
})
```
**Need for tool calls:**
```python
# Need to emit message-type events
await event_emitter({
"type": "message",
"data": {
"role": "tool",
"content": "..."
}
})
```
**Question:** Does `__event_emitter__` support `message` type events?
### 3. Session SDK Events vs OpenWebUI Messages
**Copilot SDK events:**
- `tool.execution_start` → We get tool name, arguments
- `tool.execution_complete` → We get tool result
- Designed for streaming text output
**OpenWebUI messages:**
- Expect structured message objects
- Not designed for mid-stream injection
---
## 🧪 Experimental Implementation
### Step 1: Add Valve for Native Display
```python
class Valves(BaseModel):
USE_NATIVE_TOOL_DISPLAY: bool = Field(
default=False,
description="Use OpenWebUI's native tool call display instead of markdown formatting"
)
```
### Step 2: Modify Tool Event Handling
```python
async def stream_response(self, ...):
# ...existing code...
def handler(event):
event_type = get_event_type(event)
if event_type == "tool.execution_start":
tool_name = safe_get_data_attr(event, "name")
# Get tool arguments
tool_input = safe_get_data_attr(event, "input") or {}
tool_call_id = safe_get_data_attr(event, "tool_call_id", f"call_{time.time()}")
if tool_call_id:
active_tools[tool_call_id] = {
"name": tool_name,
"arguments": tool_input
}
if self.valves.USE_NATIVE_TOOL_DISPLAY:
# Emit structured tool call
asyncio.create_task(
self._emit_tool_call_start(
__event_call__,
tool_call_id,
tool_name,
tool_input
)
)
else:
# Current markdown display
queue.put_nowait(f"\n\n> 🔧 **Running Tool**: `{tool_name}`\n\n")
elif event_type == "tool.execution_complete":
tool_call_id = safe_get_data_attr(event, "tool_call_id")
tool_info = active_tools.get(tool_call_id, {})
tool_name = tool_info.get("name", "Unknown")
# Extract result
result_obj = safe_get_data_attr(event, "result")
result_content = ""
if hasattr(result_obj, "content"):
result_content = result_obj.content
elif isinstance(result_obj, dict):
result_content = result_obj.get("content", "")
if self.valves.USE_NATIVE_TOOL_DISPLAY:
# Emit structured tool result
asyncio.create_task(
self._emit_tool_call_result(
__event_call__,
tool_call_id,
tool_name,
result_content
)
)
else:
# Current markdown display
queue.put_nowait(f"> ✅ **Tool Completed**: {result_content}\n\n")
```
---
## 🔬 Testing Plan
### Test 1: Event Emitter Message Type Support
```python
# In a test conversation, try:
await __event_emitter__({
"type": "message",
"data": {
"role": "assistant",
"content": "Test message"
}
})
```
**Expected:** Message appears in chat
**If fails:** Event emitter doesn't support message type
### Test 2: Tool Call Message Format
```python
# Send a tool call message
await __event_emitter__({
"type": "message",
"data": {
"role": "assistant",
"content": None,
"tool_calls": [{
"id": "test_123",
"type": "function",
"function": {
"name": "test_tool",
"arguments": '{"param": "value"}'
}
}]
}
})
# Send tool result
await __event_emitter__({
"type": "message",
"data": {
"role": "tool",
"tool_call_id": "test_123",
"name": "test_tool",
"content": '{"result": "success"}'
}
})
```
**Expected:** OpenWebUI displays collapsible tool panel
**If fails:** Event format doesn't match OpenWebUI expectations
### Test 3: Mid-Stream Tool Call Injection
Test if tool call messages can be injected during streaming:
```python
# Start streaming text
yield "Processing your request..."
# Mid-stream: emit tool call
await __event_emitter__({"type": "message", "data": {...}})
# Continue streaming
yield "Done!"
```
**Expected:** Tool panel appears mid-response
**Risk:** May break streaming flow
---
## 📋 Implementation Checklist
- [x] Add `REASONING_EFFORT` valve (completed)
- [ ] Add `USE_NATIVE_TOOL_DISPLAY` valve
- [ ] Implement `_emit_tool_call_start()` helper
- [ ] Implement `_emit_tool_call_result()` helper
- [ ] Modify tool event handling in `stream_response()`
- [ ] Test event emitter message type support
- [ ] Test tool call message format
- [ ] Test mid-stream injection
- [ ] Update documentation
- [ ] Add user configuration guide
---
## 🤔 Recommendation
### Hybrid Approach (Safest)
Keep both display modes:
1. **Default (Current):** Markdown-based display
- ✅ Works reliably with streaming
- ✅ No OpenWebUI API dependencies
- ✅ Consistent across versions
2. **Experimental (Native):** Structured tool messages
- ✅ Better visual integration
- ⚠️ Requires testing with OpenWebUI internals
- ⚠️ May not work in all scenarios
**Configuration:**
```python
USE_NATIVE_TOOL_DISPLAY: bool = Field(
default=False,
description="[EXPERIMENTAL] Use OpenWebUI's native tool call display"
)
```
### Why Markdown Display is Currently Better
1. **Reliability:** Always works with streaming
2. **Flexibility:** Can customize format easily
3. **Context:** Shows tools inline with reasoning
4. **Compatibility:** Works across OpenWebUI versions
### When to Use Native Display
- Non-streaming mode (easier to inject messages)
- After confirming event emitter supports message type
- For tools with large JSON results (better formatting)
---
## 📚 Next Steps
1. **Research OpenWebUI Source Code**
- Check `__event_emitter__` implementation
- Verify supported event types
- Test message injection patterns
2. **Create Proof of Concept**
- Simple test plugin
- Emit tool call messages
- Verify UI rendering
3. **Document Findings**
- Update this guide with test results
- Add code examples that work
- Create migration guide if successful
---
## 🔗 References
- [OpenAI Chat Completion API](https://platform.openai.com/docs/api-reference/chat/create)
- [OpenWebUI Plugin Development](https://docs.openwebui.com/)
- [Copilot SDK Events](https://github.com/github/copilot-sdk)
---
**Author:** Fu-Jie
**Status:** Analysis Complete - Implementation Pending Testing

View File

@@ -1,480 +0,0 @@
# OpenWebUI 原生工具调用展示实现指南
**日期:** 2026-01-27
**目的:** 分析并实现 OpenWebUI 的原生工具调用展示机制
---
## 📸 当前展示 vs 原生展示
### 当前实现
```markdown
> 🔧 **Running Tool**: `search_chats`
> ✅ **Tool Completed**: {...}
```
### OpenWebUI 原生展示(来自截图)
- ✅ 可折叠面板:"查看来自 search_chats 的结果"
- ✅ 格式化的 JSON 显示
- ✅ 语法高亮
- ✅ 展开/折叠功能
- ✅ 清晰的视觉分隔
---
## 🔍 理解 OpenWebUI 的工具调用格式
### 标准 OpenAI 工具调用消息格式
OpenWebUI 遵循 OpenAI Chat Completion API 的工具调用格式:
#### 1. 带工具调用的助手消息
```python
{
"role": "assistant",
"content": None, # 或解释性文本
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "search_chats",
"arguments": '{"query": ""}'
}
}
]
}
```
#### 2. 工具响应消息
```python
{
"role": "tool",
"tool_call_id": "call_abc123",
"name": "search_chats", # 可选但推荐
"content": '{"count": 5, "results": [...]}' # JSON 字符串
}
```
---
## 🎯 原生展示的实现策略
### 方案 1事件发射器方法推荐
使用 OpenWebUI 的事件发射器发送结构化工具调用数据:
```python
async def stream_response(self, ...):
# 工具执行开始时
if event_type == "tool.execution_start":
await self._emit_tool_call_start(
emitter=__event_call__,
tool_call_id=tool_call_id,
tool_name=tool_name,
arguments=arguments
)
# 工具执行完成时
elif event_type == "tool.execution_complete":
await self._emit_tool_call_result(
emitter=__event_call__,
tool_call_id=tool_call_id,
tool_name=tool_name,
result=result_content
)
```
#### 辅助方法
```python
async def _emit_tool_call_start(
self,
emitter: Optional[Callable[[Any], Awaitable[None]]],
tool_call_id: str,
tool_name: str,
arguments: dict
):
"""向 OpenWebUI 发射工具调用开始事件。"""
if not emitter:
return
try:
# OpenWebUI 期望 assistant 消息格式的 tool_calls
await emitter({
"type": "message",
"data": {
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": tool_call_id,
"type": "function",
"function": {
"name": tool_name,
"arguments": json.dumps(arguments, ensure_ascii=False)
}
}
]
}
})
except Exception as e:
logger.error(f"发射工具调用开始事件失败: {e}")
async def _emit_tool_call_result(
self,
emitter: Optional[Callable[[Any], Awaitable[None]]],
tool_call_id: str,
tool_name: str,
result: Any
):
"""向 OpenWebUI 发射工具调用结果。"""
if not emitter:
return
try:
# 将结果格式化为 JSON 字符串
if isinstance(result, str):
result_content = result
else:
result_content = json.dumps(result, ensure_ascii=False, indent=2)
# OpenWebUI 期望 tool 消息格式的工具结果
await emitter({
"type": "message",
"data": {
"role": "tool",
"tool_call_id": tool_call_id,
"name": tool_name,
"content": result_content
}
})
except Exception as e:
logger.error(f"发射工具结果失败: {e}")
```
### 方案 2消息历史注入
修改对话历史以包含工具调用:
```python
# 工具执行后,追加到消息中
messages.append({
"role": "assistant",
"content": None,
"tool_calls": [{
"id": tool_call_id,
"type": "function",
"function": {
"name": tool_name,
"arguments": json.dumps(arguments)
}
}]
})
messages.append({
"role": "tool",
"tool_call_id": tool_call_id,
"name": tool_name,
"content": json.dumps(result)
})
```
---
## ⚠️ 当前架构的挑战
### 1. 流式上下文
我们当前的实现使用:
- **基于队列的流式传输**:事件 → 队列 → 产出块
- **仅文本块**:我们产出纯文本,而非结构化消息
OpenWebUI 的原生展示需要:
- **结构化消息事件**:不是文本块
- **消息级别控制**:需要发射完整消息
### 2. 事件发射器兼容性
**当前用法:**
```python
# 我们使用 event_emitter 发送状态/通知
await event_emitter({
"type": "status",
"data": {"description": "处理中..."}
})
```
**工具调用所需:**
```python
# 需要发射 message 类型事件
await event_emitter({
"type": "message",
"data": {
"role": "tool",
"content": "..."
}
})
```
**问题:** `__event_emitter__` 是否支持 `message` 类型事件?
### 3. Session SDK 事件 vs OpenWebUI 消息
**Copilot SDK 事件:**
- `tool.execution_start` → 获取工具名称、参数
- `tool.execution_complete` → 获取工具结果
- 为流式文本输出设计
**OpenWebUI 消息:**
- 期望结构化消息对象
- 不为中间流注入设计
---
## 🧪 实验性实现
### 步骤 1添加原生展示 Valve
```python
class Valves(BaseModel):
USE_NATIVE_TOOL_DISPLAY: bool = Field(
default=False,
description="使用 OpenWebUI 的原生工具调用展示,而非 Markdown 格式"
)
```
### 步骤 2修改工具事件处理
```python
async def stream_response(self, ...):
# ...现有代码...
def handler(event):
event_type = get_event_type(event)
if event_type == "tool.execution_start":
tool_name = safe_get_data_attr(event, "name")
# 获取工具参数
tool_input = safe_get_data_attr(event, "input") or {}
tool_call_id = safe_get_data_attr(event, "tool_call_id", f"call_{time.time()}")
if tool_call_id:
active_tools[tool_call_id] = {
"name": tool_name,
"arguments": tool_input
}
if self.valves.USE_NATIVE_TOOL_DISPLAY:
# 发射结构化工具调用
asyncio.create_task(
self._emit_tool_call_start(
__event_call__,
tool_call_id,
tool_name,
tool_input
)
)
else:
# 当前 Markdown 展示
queue.put_nowait(f"\n\n> 🔧 **运行工具**: `{tool_name}`\n\n")
elif event_type == "tool.execution_complete":
tool_call_id = safe_get_data_attr(event, "tool_call_id")
tool_info = active_tools.get(tool_call_id, {})
tool_name = tool_info.get("name", "未知")
# 提取结果
result_obj = safe_get_data_attr(event, "result")
result_content = ""
if hasattr(result_obj, "content"):
result_content = result_obj.content
elif isinstance(result_obj, dict):
result_content = result_obj.get("content", "")
if self.valves.USE_NATIVE_TOOL_DISPLAY:
# 发射结构化工具结果
asyncio.create_task(
self._emit_tool_call_result(
__event_call__,
tool_call_id,
tool_name,
result_content
)
)
else:
# 当前 Markdown 展示
queue.put_nowait(f"> ✅ **工具完成**: {result_content}\n\n")
```
---
## 🔬 测试计划
### 测试 1事件发射器消息类型支持
```python
# 在测试对话中尝试:
await __event_emitter__({
"type": "message",
"data": {
"role": "assistant",
"content": "测试消息"
}
})
```
**预期:** 消息出现在聊天中
**如果失败:** 事件发射器不支持 message 类型
### 测试 2工具调用消息格式
```python
# 发送工具调用消息
await __event_emitter__({
"type": "message",
"data": {
"role": "assistant",
"content": None,
"tool_calls": [{
"id": "test_123",
"type": "function",
"function": {
"name": "test_tool",
"arguments": '{"param": "value"}'
}
}]
}
})
# 发送工具结果
await __event_emitter__({
"type": "message",
"data": {
"role": "tool",
"tool_call_id": "test_123",
"name": "test_tool",
"content": '{"result": "success"}'
}
})
```
**预期:** OpenWebUI 显示可折叠工具面板
**如果失败:** 事件格式与 OpenWebUI 期望不符
### 测试 3中间流工具调用注入
测试是否可以在流式传输期间注入工具调用消息:
```python
# 开始流式文本
yield "正在处理您的请求..."
# 中间流:发射工具调用
await __event_emitter__({"type": "message", "data": {...}})
# 继续流式传输
yield "完成!"
```
**预期:** 工具面板出现在响应中间
**风险:** 可能破坏流式传输流程
---
## 📋 实施检查清单
- [x] 添加 `REASONING_EFFORT` valve已完成
- [ ] 添加 `USE_NATIVE_TOOL_DISPLAY` valve
- [ ] 实现 `_emit_tool_call_start()` 辅助方法
- [ ] 实现 `_emit_tool_call_result()` 辅助方法
- [ ] 修改 `stream_response()` 中的工具事件处理
- [ ] 测试事件发射器消息类型支持
- [ ] 测试工具调用消息格式
- [ ] 测试中间流注入
- [ ] 更新文档
- [ ] 添加用户配置指南
---
## 🤔 建议
### 混合方法(最安全)
保留两种展示模式:
1. **默认(当前):** 基于 Markdown 的展示
- ✅ 与流式传输可靠工作
- ✅ 无 OpenWebUI API 依赖
- ✅ 跨版本一致
2. **实验性(原生):** 结构化工具消息
- ✅ 更好的视觉集成
- ⚠️ 需要测试 OpenWebUI 内部
- ⚠️ 可能不适用于所有场景
**配置:**
```python
USE_NATIVE_TOOL_DISPLAY: bool = Field(
default=False,
description="[实验性] 使用 OpenWebUI 的原生工具调用展示"
)
```
### 为什么 Markdown 展示目前更好
1. **可靠性:** 始终与流式传输兼容
2. **灵活性:** 可以轻松自定义格式
3. **上下文:** 与推理内联显示工具
4. **兼容性:** 跨 OpenWebUI 版本工作
### 何时使用原生展示
- 非流式模式(更容易注入消息)
- 确认事件发射器支持 message 类型后
- 对于具有大型 JSON 结果的工具(更好的格式化)
---
## 📚 后续步骤
1. **研究 OpenWebUI 源代码**
- 检查 `__event_emitter__` 实现
- 验证支持的事件类型
- 测试消息注入模式
2. **创建概念验证**
- 简单测试插件
- 发射工具调用消息
- 验证 UI 渲染
3. **记录发现**
- 使用测试结果更新本指南
- 添加有效的代码示例
- 如果成功,创建迁移指南
---
## 🔗 参考资料
- [OpenAI Chat Completion API](https://platform.openai.com/docs/api-reference/chat/create)
- [OpenWebUI 插件开发](https://docs.openwebui.com/)
- [Copilot SDK 事件](https://github.com/github/copilot-sdk)
---
**作者:** Fu-Jie
**状态:** 分析完成 - 实施等待测试

View File

@@ -1,182 +0,0 @@
# Native Tool Display Usage Guide
## 🎨 What is Native Tool Display?
Native Tool Display is an experimental feature that integrates with OpenWebUI's built-in tool call visualization system. When enabled, tool calls and their results are displayed in **collapsible JSON panels** instead of plain markdown text.
### Visual Comparison
**Traditional Display (markdown):**
```
> 🔧 Running Tool: `get_current_time`
> ✅ Tool Completed: 2026-01-27 10:30:00
```
**Native Display (collapsible panels):**
- Tool call appears in a collapsible `assistant.tool_calls` panel
- Tool result appears in a separate collapsible `tool.content` panel
- JSON syntax highlighting for better readability
- Compact by default, expandable on click
## 🚀 How to Enable
1. Open the GitHub Copilot SDK Pipe configuration (Valves)
2. Set `USE_NATIVE_TOOL_DISPLAY` to `true`
3. Save the configuration
4. Start a new conversation with tool calls
## 📋 Requirements
- OpenWebUI with native tool display support
- `__event_emitter__` must support `message` type events
- Tool-enabled models (e.g., GPT-4, Claude Sonnet)
## ⚙️ How It Works
### OpenAI Standard Format
The native display uses the OpenAI standard message format:
**Tool Call (Assistant Message):**
```json
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_current_time",
"arguments": "{\"timezone\":\"UTC\"}"
}
}
]
}
```
**Tool Result (Tool Message):**
```json
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "2026-01-27 10:30:00 UTC"
}
```
### Message Flow
1. **Tool Execution Start**:
- SDK emits `tool.execution_start` event
- Plugin sends `assistant` message with `tool_calls` array
- OpenWebUI displays collapsible tool call panel
2. **Tool Execution Complete**:
- SDK emits `tool.execution_complete` event
- Plugin sends `tool` message with `tool_call_id` and `content`
- OpenWebUI displays collapsible result panel
## 🔧 Troubleshooting
### Panel Not Showing
**Symptoms:** Tool calls still appear as markdown text
**Possible Causes:**
1. `__event_emitter__` doesn't support `message` type events
2. OpenWebUI version too old
3. Feature not enabled (`USE_NATIVE_TOOL_DISPLAY = false`)
**Solution:**
- Enable DEBUG mode to see error messages in browser console
- Check browser console for "Native message emission failed" warnings
- Update OpenWebUI to latest version
- Keep `USE_NATIVE_TOOL_DISPLAY = false` to use traditional markdown display
### Duplicate Tool Information
**Symptoms:** Tool calls appear in both native panels and markdown
**Cause:** Mixed display modes
**Solution:**
- Ensure `USE_NATIVE_TOOL_DISPLAY` is either `true` (native only) or `false` (markdown only)
- Restart the conversation after changing this setting
## 🧪 Experimental Status
This feature is marked as **EXPERIMENTAL** because:
1. **Event Emitter API**: The `__event_emitter__` support for `message` type events is not fully documented
2. **OpenWebUI Version Dependency**: Requires recent versions of OpenWebUI with native tool display support
3. **Streaming Architecture**: May have compatibility issues with streaming responses
### Fallback Behavior
If native message emission fails:
- Plugin automatically falls back to markdown display
- Error logged to browser console (when DEBUG is enabled)
- No interruption to conversation flow
## 📊 Performance Considerations
Native display has slightly better performance characteristics:
| Aspect | Native Display | Markdown Display |
|--------|----------------|------------------|
| **Rendering** | Native UI components | Markdown parser |
| **Interactivity** | Collapsible panels | Static text |
| **JSON Parsing** | Handled by UI | Not formatted |
| **Token Usage** | Minimal overhead | Formatting tokens |
## 🔮 Future Enhancements
Planned improvements for native tool display:
- [ ] Automatic fallback detection
- [ ] Tool call history persistence
- [ ] Rich metadata display (execution time, arguments preview)
- [ ] Copy tool call JSON button
- [ ] Tool call replay functionality
## 💡 Best Practices
1. **Enable DEBUG First**: Test with DEBUG mode before using in production
2. **Monitor Browser Console**: Check for warning messages during tool calls
3. **Test with Simple Tools**: Verify with built-in tools before custom implementations
4. **Keep Fallback Option**: Don't rely solely on native display until it exits experimental status
## 📖 Related Documentation
- [TOOLS_USAGE.md](TOOLS_USAGE.md) - How to create and use custom tools
- [NATIVE_TOOL_DISPLAY_GUIDE.md](NATIVE_TOOL_DISPLAY_GUIDE.md) - Technical implementation details
- [WORKFLOW.md](WORKFLOW.md) - Complete integration workflow
## 🐛 Reporting Issues
If you encounter issues with native tool display:
1. Enable `DEBUG` and `USE_NATIVE_TOOL_DISPLAY`
2. Open browser console (F12)
3. Trigger a tool call
4. Copy any error messages
5. Report to [GitHub Issues](https://github.com/Fu-Jie/openwebui-extensions/issues)
Include:
- OpenWebUI version
- Browser and version
- Error messages from console
- Steps to reproduce
---
**Author:** Fu-Jie | **Version:** 0.2.0 | **License:** MIT

View File

@@ -1,182 +0,0 @@
# 原生工具显示使用指南
## 🎨 什么是原生工具显示?
原生工具显示是一项实验性功能,与 OpenWebUI 的内置工具调用可视化系统集成。启用后,工具调用及其结果将以**可折叠的 JSON 面板**显示,而不是纯文本 markdown。
### 视觉对比
**传统显示 (markdown):**
```
> 🔧 正在运行工具: `get_current_time`
> ✅ 工具已完成: 2026-01-27 10:30:00
```
**原生显示 (可折叠面板):**
- 工具调用显示在可折叠的 `assistant.tool_calls` 面板中
- 工具结果显示在单独的可折叠 `tool.content` 面板中
- JSON 语法高亮,提高可读性
- 默认折叠,点击即可展开
## 🚀 如何启用
1. 打开 GitHub Copilot SDK Pipe 配置 (Valves)
2.`USE_NATIVE_TOOL_DISPLAY` 设置为 `true`
3. 保存配置
4. 开始新的对话并使用工具调用
## 📋 要求
- 支持原生工具显示的 OpenWebUI
- `__event_emitter__` 必须支持 `message` 类型事件
- 支持工具的模型(例如 GPT-4、Claude Sonnet
## ⚙️ 工作原理
### OpenAI 标准格式
原生显示使用 OpenAI 标准消息格式:
**工具调用(助手消息):**
```json
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_current_time",
"arguments": "{\"timezone\":\"UTC\"}"
}
}
]
}
```
**工具结果(工具消息):**
```json
{
"role": "tool",
"tool_call_id": "call_abc123",
"content": "2026-01-27 10:30:00 UTC"
}
```
### 消息流程
1. **工具执行开始**
- SDK 发出 `tool.execution_start` 事件
- 插件发送带有 `tool_calls` 数组的 `assistant` 消息
- OpenWebUI 显示可折叠的工具调用面板
2. **工具执行完成**
- SDK 发出 `tool.execution_complete` 事件
- 插件发送带有 `tool_call_id``content``tool` 消息
- OpenWebUI 显示可折叠的结果面板
## 🔧 故障排除
### 面板未显示
**症状:** 工具调用仍以 markdown 文本形式显示
**可能原因:**
1. `__event_emitter__` 不支持 `message` 类型事件
2. OpenWebUI 版本过旧
3. 功能未启用(`USE_NATIVE_TOOL_DISPLAY = false`
**解决方案:**
- 启用 DEBUG 模式查看浏览器控制台中的错误消息
- 检查浏览器控制台的 "Native message emission failed" 警告
- 更新 OpenWebUI 到最新版本
- 保持 `USE_NATIVE_TOOL_DISPLAY = false` 使用传统 markdown 显示
### 重复的工具信息
**症状:** 工具调用同时出现在原生面板和 markdown 中
**原因:** 混合显示模式
**解决方案:**
- 确保 `USE_NATIVE_TOOL_DISPLAY``true`(仅原生)或 `false`(仅 markdown
- 更改设置后重启对话
## 🧪 实验性状态
此功能标记为**实验性**,因为:
1. **事件发射器 API**`__event_emitter__``message` 类型事件的支持未完全文档化
2. **OpenWebUI 版本依赖**:需要支持原生工具显示的较新 OpenWebUI 版本
3. **流式架构**:可能与流式响应存在兼容性问题
### 回退行为
如果原生消息发送失败:
- 插件自动回退到 markdown 显示
- 错误记录到浏览器控制台(启用 DEBUG 时)
- 不会中断对话流程
## 📊 性能考虑
原生显示具有略好的性能特征:
| 方面 | 原生显示 | Markdown 显示 |
|------|----------|---------------|
| **渲染** | 原生 UI 组件 | Markdown 解析器 |
| **交互性** | 可折叠面板 | 静态文本 |
| **JSON 解析** | 由 UI 处理 | 未格式化 |
| **Token 使用** | 最小开销 | 格式化 token |
## 🔮 未来增强
原生工具显示的计划改进:
- [ ] 自动回退检测
- [ ] 工具调用历史持久化
- [ ] 丰富的元数据显示(执行时间、参数预览)
- [ ] 复制工具调用 JSON 按钮
- [ ] 工具调用重放功能
## 💡 最佳实践
1. **先启用 DEBUG**:在生产环境使用前先在 DEBUG 模式下测试
2. **监控浏览器控制台**:在工具调用期间检查警告消息
3. **使用简单工具测试**:在自定义实现前先用内置工具验证
4. **保留回退选项**:在退出实验性状态前不要完全依赖原生显示
## 📖 相关文档
- [TOOLS_USAGE.md](TOOLS_USAGE.md) - 如何创建和使用自定义工具
- [NATIVE_TOOL_DISPLAY_GUIDE.md](NATIVE_TOOL_DISPLAY_GUIDE.md) - 技术实现细节
- [WORKFLOW.md](WORKFLOW.md) - 完整集成工作流程
## 🐛 报告问题
如果您在使用原生工具显示时遇到问题:
1. 启用 `DEBUG``USE_NATIVE_TOOL_DISPLAY`
2. 打开浏览器控制台F12
3. 触发工具调用
4. 复制任何错误消息
5. 报告到 [GitHub Issues](https://github.com/Fu-Jie/openwebui-extensions/issues)
包含:
- OpenWebUI 版本
- 浏览器和版本
- 控制台的错误消息
- 复现步骤
---
**作者:** Fu-Jie | **版本:** 0.2.0 | **许可证:** MIT

View File

@@ -1,509 +0,0 @@
# OpenWebUI Function 集成方案
## 🎯 核心挑战
在 Copilot Tool Handler 中调用 OpenWebUI Functions 的关键问题:
**问题:** Copilot SDK 的 Tool Handler 是一个独立的回调函数,如何在这个上下文中访问和执行 OpenWebUI 的 Function
---
## 🔍 OpenWebUI Function 系统分析
### 1. Function 数据结构
OpenWebUI 的 Function/Tool 传递格式:
```python
body = {
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather",
"parameters": {
"type": "object",
"properties": {
"location": {"type": "string"}
},
"required": ["location"]
}
}
}
]
}
```
### 2. Function 执行机制
OpenWebUI Functions 的执行方式有几种可能:
#### 选项 A: 通过 Function ID 调用内部 API
```python
# 假设 OpenWebUI 提供内部 API
from open_webui.apps.webui.models.functions import Functions
function_id = "function_uuid" # 需要从配置中获取
result = await Functions.execute_function(
function_id=function_id,
arguments={"location": "Beijing"}
)
```
#### 选项 B: 通过 **event_emitter** 触发
```python
# 通过事件系统触发 function 执行
if __event_emitter__:
await __event_emitter__({
"type": "function_call",
"data": {
"name": "get_weather",
"arguments": {"location": "Beijing"}
}
})
```
#### 选项 C: 自己实现 Function 逻辑
```python
# 在 Pipe 内部实现常用功能
class Pipe:
def _builtin_get_weather(self, location: str) -> dict:
# 实现天气查询
pass
def _builtin_search_web(self, query: str) -> dict:
# 实现网页搜索
pass
```
---
## 💡 推荐方案:混合架构
### 架构设计
```
User Message
OpenWebUI UI (Functions 已配置)
Pipe.pipe(body) - body 包含 tools[]
转换为 Copilot Tools + 存储 Function Registry
Copilot 决定调用 Tool
Tool Handler 查询 Registry → 执行对应逻辑
返回结果给 Copilot
继续生成回答
```
### 核心实现
#### 1. Function Registry函数注册表
```python
class Pipe:
def __init__(self):
# ...
self._function_registry = {} # {function_name: callable}
self._function_metadata = {} # {function_name: metadata}
```
#### 2. 注册 Functions
```python
def _register_openwebui_functions(
self,
owui_functions: List[dict],
__event_emitter__=None,
__event_call__=None
):
"""
注册 OpenWebUI Functions 到内部 registry
关键:将 function 定义和执行逻辑关联起来
"""
for func_def in owui_functions:
if func_def.get("type") != "function":
continue
func_info = func_def.get("function", {})
func_name = func_info.get("name")
if not func_name:
continue
# 存储元数据
self._function_metadata[func_name] = {
"description": func_info.get("description", ""),
"parameters": func_info.get("parameters", {}),
"original_def": func_def
}
# 创建执行器(关键)
executor = self._create_function_executor(
func_name,
func_def,
__event_emitter__,
__event_call__
)
self._function_registry[func_name] = executor
```
#### 3. Function Executor 工厂
```python
def _create_function_executor(
self,
func_name: str,
func_def: dict,
__event_emitter__=None,
__event_call__=None
):
"""
为每个 function 创建执行器
策略:
1. 优先使用内置实现
2. 尝试调用 OpenWebUI API
3. 返回错误
"""
async def executor(arguments: dict) -> dict:
# 策略 1: 检查是否有内置实现
builtin_method = getattr(self, f"_builtin_{func_name}", None)
if builtin_method:
self._emit_debug_log_sync(
f"Using builtin implementation for {func_name}",
__event_call__
)
try:
result = builtin_method(arguments)
if inspect.iscoroutine(result):
result = await result
return {"success": True, "result": result}
except Exception as e:
return {"success": False, "error": str(e)}
# 策略 2: 尝试通过 Event Emitter 调用
if __event_emitter__:
try:
# 尝试触发 function_call 事件
response_queue = asyncio.Queue()
await __event_emitter__({
"type": "function_call",
"data": {
"name": func_name,
"arguments": arguments,
"response_queue": response_queue # 回调队列
}
})
# 等待结果(带超时)
result = await asyncio.wait_for(
response_queue.get(),
timeout=self.valves.TOOL_TIMEOUT
)
return {"success": True, "result": result}
except asyncio.TimeoutError:
return {"success": False, "error": "Function execution timeout"}
except Exception as e:
self._emit_debug_log_sync(
f"Event emitter call failed: {e}",
__event_call__
)
# 继续尝试其他方法
# 策略 3: 尝试调用 OpenWebUI internal API
try:
# 这需要研究 OpenWebUI 源码确定正确的调用方式
from open_webui.apps.webui.models.functions import Functions
# 需要获取 function_id这是关键问题
function_id = self._get_function_id_by_name(func_name)
if function_id:
result = await Functions.execute(
function_id=function_id,
params=arguments
)
return {"success": True, "result": result}
except ImportError:
pass
except Exception as e:
self._emit_debug_log_sync(
f"OpenWebUI API call failed: {e}",
__event_call__
)
# 策略 4: 返回"未实现"错误
return {
"success": False,
"error": f"Function '{func_name}' is not implemented. "
"Please implement it as a builtin method or ensure OpenWebUI API is available."
}
return executor
```
#### 4. Tool Handler 实现
```python
def _create_tool_handler(self, tool_name: str, __event_call__=None):
"""为 Copilot SDK 创建 Tool Handler"""
async def handler(invocation: dict) -> dict:
"""
Copilot Tool Handler
invocation: {
"session_id": str,
"tool_call_id": str,
"tool_name": str,
"arguments": dict
}
"""
try:
# 从 registry 获取 executor
executor = self._function_registry.get(invocation["tool_name"])
if not executor:
return {
"textResultForLlm": f"Function '{invocation['tool_name']}' not found.",
"resultType": "failure",
"error": "function_not_found",
"toolTelemetry": {}
}
# 执行 function
self._emit_debug_log_sync(
f"Executing function: {invocation['tool_name']}({invocation['arguments']})",
__event_call__
)
exec_result = await executor(invocation["arguments"])
# 处理结果
if exec_result.get("success"):
result_text = str(exec_result.get("result", ""))
return {
"textResultForLlm": result_text,
"resultType": "success",
"error": None,
"toolTelemetry": {}
}
else:
error_msg = exec_result.get("error", "Unknown error")
return {
"textResultForLlm": f"Function execution failed: {error_msg}",
"resultType": "failure",
"error": error_msg,
"toolTelemetry": {}
}
except Exception as e:
self._emit_debug_log_sync(
f"Tool handler error: {e}",
__event_call__
)
return {
"textResultForLlm": "An unexpected error occurred during function execution.",
"resultType": "failure",
"error": str(e),
"toolTelemetry": {}
}
return handler
```
---
## 🔌 内置 Functions 实现示例
### 示例 1: 获取当前时间
```python
def _builtin_get_current_time(self, arguments: dict) -> str:
"""内置实现:获取当前时间"""
from datetime import datetime
timezone = arguments.get("timezone", "UTC")
format_str = arguments.get("format", "%Y-%m-%d %H:%M:%S")
now = datetime.now()
return now.strftime(format_str)
```
### 示例 2: 简单计算器
```python
def _builtin_calculate(self, arguments: dict) -> str:
"""内置实现:数学计算"""
expression = arguments.get("expression", "")
try:
# 安全的数学计算(仅允许基本运算)
allowed_chars = set("0123456789+-*/()., ")
if not all(c in allowed_chars for c in expression):
raise ValueError("Invalid characters in expression")
result = eval(expression, {"__builtins__": {}})
return str(result)
except Exception as e:
raise ValueError(f"Calculation error: {e}")
```
### 示例 3: 网页搜索(需要外部 API
```python
async def _builtin_search_web(self, arguments: dict) -> str:
"""内置实现:网页搜索(使用 DuckDuckGo"""
query = arguments.get("query", "")
max_results = arguments.get("max_results", 5)
try:
# 使用 duckduckgo_search 库
from duckduckgo_search import DDGS
results = []
with DDGS() as ddgs:
for r in ddgs.text(query, max_results=max_results):
results.append({
"title": r.get("title", ""),
"url": r.get("href", ""),
"snippet": r.get("body", "")
})
# 格式化结果
formatted = "\n\n".join([
f"**{r['title']}**\n{r['url']}\n{r['snippet']}"
for r in results
])
return formatted
except Exception as e:
raise ValueError(f"Search failed: {e}")
```
---
## 🚀 完整集成流程
### pipe() 方法中的集成
```python
async def pipe(
self,
body: dict,
__metadata__: Optional[dict] = None,
__event_emitter__=None,
__event_call__=None,
) -> Union[str, AsyncGenerator]:
# ... 现有代码 ...
# ✅ Step 1: 提取 OpenWebUI Functions
owui_functions = body.get("tools", [])
# ✅ Step 2: 注册 Functions
if self.valves.ENABLE_TOOLS and owui_functions:
self._register_openwebui_functions(
owui_functions,
__event_emitter__,
__event_call__
)
# ✅ Step 3: 转换为 Copilot Tools
copilot_tools = []
for func_name in self._function_registry.keys():
metadata = self._function_metadata[func_name]
copilot_tools.append({
"name": func_name,
"description": metadata["description"],
"parameters": metadata["parameters"],
"handler": self._create_tool_handler(func_name, __event_call__)
})
# ✅ Step 4: 创建 Session 并传递 Tools
session_config = SessionConfig(
model=real_model_id,
tools=copilot_tools, # ✅ 关键
...
)
session = await client.create_session(config=session_config)
# ... 后续代码 ...
```
---
## ⚠️ 待解决问题
### 1. Function ID 映射
**问题:** OpenWebUI Functions 通常通过 UUID 标识,但 body 中只有 name
**解决思路:**
- 在 OpenWebUI 启动时建立 name → id 映射表
- 或者修改 OpenWebUI 在 body 中同时传递 id
### 2. Event Emitter 回调机制
**问题:** 不确定 **event_emitter** 是否支持 function_call 事件
**验证方法:**
```python
# 测试代码
await __event_emitter__({
"type": "function_call",
"data": {"name": "test_func", "arguments": {}}
})
```
### 3. 异步执行超时
**问题:** 某些 Functions 可能执行很慢
**解决方案:**
- 实现 timeout 机制(已在 executor 中实现)
- 对于长时间运行的任务,考虑返回"processing"状态
---
## 📝 实现清单
- [ ] 实现 _function_registry 和 _function_metadata
- [ ] 实现 _register_openwebui_functions()
- [ ] 实现 _create_function_executor()
- [ ] 实现 _create_tool_handler()
- [ ] 实现 3-5 个常用内置 Functions
- [ ] 测试 Function 注册和调用流程
- [ ] 验证 **event_emitter** 机制
- [ ] 研究 OpenWebUI Functions API
- [ ] 添加错误处理和超时机制
- [ ] 更新文档
---
**下一步行动:**
1. 实现基础的 Function Registry
2. 添加 2-3 个简单的内置 Functions如 get_time, calculate
3. 测试基本的 Tool Calling 流程
4. 根据测试结果调整架构
**作者:** Fu-Jie
**日期:** 2026-01-26

View File

@@ -1,708 +0,0 @@
# SessionConfig 完整功能集成指南
## 📋 概述
本文档详细说明如何将 GitHub Copilot SDK 的 `SessionConfig` 所有功能集成到 OpenWebUI Pipe 中。
---
## 🎯 功能清单与集成状态
| 功能 | 状态 | 优先级 | 说明 |
|------|------|--------|------|
| `session_id` | ✅ 已实现 | 高 | 使用 OpenWebUI chat_id |
| `model` | ✅ 已实现 | 高 | 从 body 动态获取 |
| `tools` | ✅ 已实现 | 高 | v0.2.0 新增示例工具 |
| `streaming` | ✅ 已实现 | 高 | 支持流式输出 |
| `infinite_sessions` | ✅ 已实现 | 高 | 自动上下文压缩 |
| `system_message` | ⚠️ 部分支持 | 中 | 可通过 Valves 添加 |
| `available_tools` | ⚠️ 部分支持 | 中 | 已有 AVAILABLE_TOOLS |
| `excluded_tools` | 🔲 未实现 | 低 | 可添加到 Valves |
| `on_permission_request` | 🔲 未实现 | 中 | 需要 UI 交互支持 |
| `provider` (BYOK) | 🔲 未实现 | 低 | 高级功能 |
| `mcp_servers` | 🔲 未实现 | 低 | MCP 协议支持 |
| `custom_agents` | 🔲 未实现 | 低 | 自定义代理 |
| `config_dir` | 🔲 未实现 | 低 | 可通过 WORKSPACE_DIR |
| `skill_directories` | 🔲 未实现 | 低 | 技能系统 |
| `disabled_skills` | 🔲 未实现 | 低 | 技能过滤 |
---
## 📖 详细集成方案
### 1. ✅ session_id已实现
**功能:** 持久化会话 ID
**当前实现:**
```python
session_config = SessionConfig(
session_id=chat_id if chat_id else None, # 使用 OpenWebUI 的 chat_id
...
)
```
**工作原理:**
- OpenWebUI 的 `chat_id` 直接映射为 Copilot 的 `session_id`
- 会话状态持久化到磁盘
- 支持跨重启恢复对话
---
### 2. ✅ model已实现
**功能:** 选择 Copilot 模型
**当前实现:**
```python
# 从用户选择的模型中提取
request_model = body.get("model", "")
if request_model.startswith(f"{self.id}-"):
real_model_id = request_model[len(f"{self.id}-"):]
```
**Valves 配置:**
```python
MODEL_ID: str = Field(
default="claude-sonnet-4.5",
description="默认模型(动态获取失败时使用)"
)
```
---
### 3. ✅ tools已实现 - v0.2.0
**功能:** 自定义工具/函数调用
**当前实现:**
```python
custom_tools = self._initialize_custom_tools()
session_config = SessionConfig(
tools=custom_tools,
...
)
```
**Valves 配置:**
```python
ENABLE_TOOLS: bool = Field(default=False)
AVAILABLE_TOOLS: str = Field(default="all")
```
**内置示例工具:**
- `get_current_time` - 获取当前时间
- `calculate` - 数学计算
- `generate_random_number` - 随机数生成
**扩展方法:** 参考 [TOOLS_USAGE.md](TOOLS_USAGE.md)
---
### 4. ⚠️ system_message部分支持
**功能:** 自定义系统提示词
**集成方案:**
#### 方案 A通过 Valves 添加(推荐)
```python
class Valves(BaseModel):
SYSTEM_MESSAGE: str = Field(
default="",
description="Custom system message (append mode)"
)
SYSTEM_MESSAGE_MODE: str = Field(
default="append",
description="System message mode: 'append' or 'replace'"
)
```
**实现:**
```python
async def pipe(self, body, ...):
system_message_config = None
if self.valves.SYSTEM_MESSAGE:
if self.valves.SYSTEM_MESSAGE_MODE == "replace":
system_message_config = {
"mode": "replace",
"content": self.valves.SYSTEM_MESSAGE
}
else:
system_message_config = {
"mode": "append",
"content": self.valves.SYSTEM_MESSAGE
}
session_config = SessionConfig(
system_message=system_message_config,
...
)
```
#### 方案 B从 OpenWebUI 系统提示词读取
```python
# 从 body 中获取系统提示词
system_prompt = body.get("system", "")
if system_prompt:
system_message_config = {
"mode": "append",
"content": system_prompt
}
```
**注意事项:**
- `append` 模式:在默认系统提示词后追加
- `replace` 模式:完全替换(移除 SDK 安全保护)
---
### 5. ⚠️ available_tools / excluded_tools
**功能:** 工具白名单/黑名单
**当前部分支持:**
```python
AVAILABLE_TOOLS: str = Field(
default="all",
description="'all' or comma-separated list"
)
```
**增强实现:**
```python
class Valves(BaseModel):
AVAILABLE_TOOLS: str = Field(
default="all",
description="Available tools (comma-separated or 'all')"
)
EXCLUDED_TOOLS: str = Field(
default="",
description="Excluded tools (comma-separated)"
)
```
**应用到 SessionConfig**
```python
session_config = SessionConfig(
tools=custom_tools,
available_tools=self._parse_tool_list(self.valves.AVAILABLE_TOOLS),
excluded_tools=self._parse_tool_list(self.valves.EXCLUDED_TOOLS),
...
)
def _parse_tool_list(self, value: str) -> list[str]:
"""解析工具列表"""
if not value or value == "all":
return []
return [t.strip() for t in value.split(",") if t.strip()]
```
---
### 6. 🔲 on_permission_request未实现
**功能:** 处理权限请求shell 命令、文件写入等)
**使用场景:**
- Copilot 需要执行 shell 命令
- 需要写入文件
- 需要访问 URL
**集成挑战:**
- 需要 OpenWebUI 前端支持实时权限弹窗
- 需要异步处理用户确认
**推荐方案:**
#### 方案 A自动批准开发/测试环境)
```python
async def auto_approve_permission_handler(
request: dict,
context: dict
) -> dict:
"""自动批准所有权限请求(危险!)"""
return {
"kind": "approved",
"rules": []
}
session_config = SessionConfig(
on_permission_request=auto_approve_permission_handler,
...
)
```
#### 方案 B基于规则的批准
```python
class Valves(BaseModel):
ALLOW_SHELL_COMMANDS: bool = Field(default=False)
ALLOW_FILE_WRITE: bool = Field(default=False)
ALLOW_URL_ACCESS: bool = Field(default=True)
async def rule_based_permission_handler(
request: dict,
context: dict
) -> dict:
kind = request.get("kind")
if kind == "shell" and not self.valves.ALLOW_SHELL_COMMANDS:
return {"kind": "denied-by-rules"}
if kind == "write" and not self.valves.ALLOW_FILE_WRITE:
return {"kind": "denied-by-rules"}
if kind == "url" and not self.valves.ALLOW_URL_ACCESS:
return {"kind": "denied-by-rules"}
return {"kind": "approved", "rules": []}
```
#### 方案 C通过 Event Emitter 请求用户确认(理想)
```python
async def interactive_permission_handler(
request: dict,
context: dict
) -> dict:
"""通过前端请求用户确认"""
if not __event_emitter__:
return {"kind": "denied-no-approval-rule-and-could-not-request-from-user"}
# 发送权限请求到前端
response_queue = asyncio.Queue()
await __event_emitter__({
"type": "permission_request",
"data": {
"kind": request.get("kind"),
"description": request.get("description"),
"response_queue": response_queue
}
})
# 等待用户响应(带超时)
try:
user_response = await asyncio.wait_for(
response_queue.get(),
timeout=30.0
)
if user_response.get("approved"):
return {"kind": "approved", "rules": []}
else:
return {"kind": "denied-interactively-by-user"}
except asyncio.TimeoutError:
return {"kind": "denied-no-approval-rule-and-could-not-request-from-user"}
```
---
### 7. 🔲 providerBYOK - Bring Your Own Key
**功能:** 使用自己的 API 密钥连接 OpenAI/Azure/Anthropic
**使用场景:**
- 不使用 GitHub Copilot 配额
- 直接连接云服务提供商
- 使用 Azure OpenAI 部署
**集成方案:**
```python
class Valves(BaseModel):
USE_CUSTOM_PROVIDER: bool = Field(default=False)
PROVIDER_TYPE: str = Field(
default="openai",
description="Provider type: openai, azure, anthropic"
)
PROVIDER_BASE_URL: str = Field(default="")
PROVIDER_API_KEY: str = Field(default="")
PROVIDER_BEARER_TOKEN: str = Field(default="")
AZURE_API_VERSION: str = Field(default="2024-10-21")
def _build_provider_config(self) -> dict | None:
"""构建 Provider 配置"""
if not self.valves.USE_CUSTOM_PROVIDER:
return None
config = {
"type": self.valves.PROVIDER_TYPE,
"base_url": self.valves.PROVIDER_BASE_URL,
}
if self.valves.PROVIDER_API_KEY:
config["api_key"] = self.valves.PROVIDER_API_KEY
if self.valves.PROVIDER_BEARER_TOKEN:
config["bearer_token"] = self.valves.PROVIDER_BEARER_TOKEN
if self.valves.PROVIDER_TYPE == "azure":
config["azure"] = {
"api_version": self.valves.AZURE_API_VERSION
}
# 自动推断 wire_api
if self.valves.PROVIDER_TYPE == "anthropic":
config["wire_api"] = "responses"
else:
config["wire_api"] = "completions"
return config
```
**应用:**
```python
session_config = SessionConfig(
provider=self._build_provider_config(),
...
)
```
---
### 8. ✅ streaming已实现
**功能:** 流式输出
**当前实现:**
```python
session_config = SessionConfig(
streaming=body.get("stream", False),
...
)
```
---
### 9. 🔲 mcp_serversMCP 协议)
**功能:** Model Context Protocol 服务器集成
**使用场景:**
- 连接外部数据源数据库、API
- 集成第三方服务
**集成方案:**
```python
class Valves(BaseModel):
MCP_SERVERS_CONFIG: str = Field(
default="{}",
description="MCP servers configuration (JSON format)"
)
def _parse_mcp_servers(self) -> dict | None:
"""解析 MCP 服务器配置"""
if not self.valves.MCP_SERVERS_CONFIG:
return None
try:
return json.loads(self.valves.MCP_SERVERS_CONFIG)
except:
return None
```
**配置示例:**
```json
{
"database": {
"type": "local",
"command": "mcp-server-sqlite",
"args": ["--db", "/path/to/db.sqlite"],
"tools": ["*"]
},
"weather": {
"type": "http",
"url": "https://weather-api.example.com/mcp",
"tools": ["get_weather", "get_forecast"]
}
}
```
---
### 10. 🔲 custom_agents
**功能:** 自定义 AI 代理
**使用场景:**
- 专门化的子代理(如代码审查、文档编写)
- 不同的提示词策略
**集成方案:**
```python
class Valves(BaseModel):
CUSTOM_AGENTS_CONFIG: str = Field(
default="[]",
description="Custom agents configuration (JSON array)"
)
def _parse_custom_agents(self) -> list | None:
"""解析自定义代理配置"""
if not self.valves.CUSTOM_AGENTS_CONFIG:
return None
try:
return json.loads(self.valves.CUSTOM_AGENTS_CONFIG)
except:
return None
```
**配置示例:**
```json
[
{
"name": "code_reviewer",
"display_name": "Code Reviewer",
"description": "Reviews code for best practices",
"prompt": "You are an expert code reviewer. Focus on security, performance, and maintainability.",
"tools": ["read_file", "write_file"],
"infer": true
}
]
```
---
### 11. 🔲 config_dir
**功能:** 自定义配置目录
**当前支持:**
- 已有 `WORKSPACE_DIR` 控制工作目录
**增强方案:**
```python
class Valves(BaseModel):
CONFIG_DIR: str = Field(
default="",
description="Custom config directory for session state"
)
session_config = SessionConfig(
config_dir=self.valves.CONFIG_DIR if self.valves.CONFIG_DIR else None,
...
)
```
---
### 12. 🔲 skill_directories / disabled_skills
**功能:** Copilot Skills 系统
**使用场景:**
- 加载自定义技能包
- 禁用特定技能
**集成方案:**
```python
class Valves(BaseModel):
SKILL_DIRECTORIES: str = Field(
default="",
description="Comma-separated skill directories"
)
DISABLED_SKILLS: str = Field(
default="",
description="Comma-separated disabled skills"
)
def _parse_skills_config(self):
"""解析技能配置"""
skill_dirs = []
if self.valves.SKILL_DIRECTORIES:
skill_dirs = [
d.strip()
for d in self.valves.SKILL_DIRECTORIES.split(",")
if d.strip()
]
disabled = []
if self.valves.DISABLED_SKILLS:
disabled = [
s.strip()
for s in self.valves.DISABLED_SKILLS.split(",")
if s.strip()
]
return skill_dirs, disabled
# 应用
skill_dirs, disabled_skills = self._parse_skills_config()
session_config = SessionConfig(
skill_directories=skill_dirs if skill_dirs else None,
disabled_skills=disabled_skills if disabled_skills else None,
...
)
```
---
### 13. ✅ infinite_sessions已实现
**功能:** 无限会话与自动上下文压缩
**当前实现:**
```python
class Valves(BaseModel):
INFINITE_SESSION: bool = Field(default=True)
COMPACTION_THRESHOLD: float = Field(default=0.8)
BUFFER_THRESHOLD: float = Field(default=0.95)
infinite_session_config = None
if self.valves.INFINITE_SESSION:
infinite_session_config = {
"enabled": True,
"background_compaction_threshold": self.valves.COMPACTION_THRESHOLD,
"buffer_exhaustion_threshold": self.valves.BUFFER_THRESHOLD,
}
session_config = SessionConfig(
infinite_sessions=infinite_session_config,
...
)
```
---
## 🎯 实施优先级建议
### 🔥 高优先级(立即实现)
1. **system_message** - 用户最常需要的功能
2. **on_permission_request (基于规则)** - 安全性需求
### 📌 中优先级(下一阶段)
3. **excluded_tools** - 完善工具管理
4. **provider (BYOK)** - 高级用户需求
5. **config_dir** - 增强会话管理
### 📋 低优先级(可选)
6. **mcp_servers** - 高级集成
7. **custom_agents** - 专业化功能
8. **skill_directories** - 生态系统功能
---
## 🚀 快速实施计划
### Phase 1: 基础增强1-2小时
```python
# 添加到 Valves
SYSTEM_MESSAGE: str = Field(default="")
SYSTEM_MESSAGE_MODE: str = Field(default="append")
EXCLUDED_TOOLS: str = Field(default="")
# 添加到 pipe() 方法
system_message_config = self._build_system_message_config()
excluded_tools = self._parse_tool_list(self.valves.EXCLUDED_TOOLS)
session_config = SessionConfig(
system_message=system_message_config,
excluded_tools=excluded_tools,
...
)
```
### Phase 2: 权限管理2-3小时
```python
# 添加权限控制 Valves
ALLOW_SHELL_COMMANDS: bool = Field(default=False)
ALLOW_FILE_WRITE: bool = Field(default=False)
ALLOW_URL_ACCESS: bool = Field(default=True)
# 实现权限处理器
session_config = SessionConfig(
on_permission_request=self._create_permission_handler(),
...
)
```
### Phase 3: BYOK 支持3-4小时
```python
# 添加 Provider Valves
USE_CUSTOM_PROVIDER: bool = Field(default=False)
PROVIDER_TYPE: str = Field(default="openai")
PROVIDER_BASE_URL: str = Field(default="")
PROVIDER_API_KEY: str = Field(default="")
# 实现 Provider 配置
session_config = SessionConfig(
provider=self._build_provider_config(),
...
)
```
---
## 📚 参考资源
- **SDK 类型定义**: `/opt/homebrew/.../copilot/types.py`
- **工具系统**: [TOOLS_USAGE.md](TOOLS_USAGE.md)
- **SDK 文档**: <https://github.com/github/copilot-sdk>
---
## ✅ 实施检查清单
使用此清单跟踪实施进度:
- [x] session_id
- [x] model
- [x] tools
- [x] streaming
- [x] infinite_sessions
- [ ] system_message
- [ ] available_tools (完善)
- [ ] excluded_tools
- [ ] on_permission_request
- [ ] provider (BYOK)
- [ ] mcp_servers
- [ ] custom_agents
- [ ] config_dir
- [ ] skill_directories
- [ ] disabled_skills
---
**作者:** Fu-Jie
**版本:** v1.0
**日期:** 2026-01-26
**更新:** 随功能实施持续更新

View File

@@ -1,191 +0,0 @@
# 🛠️ Custom Tools Usage / 自定义工具使用指南
## Overview / 概览
This pipe includes **1 example custom tool** that demonstrates how to use GitHub Copilot SDK's tool calling feature.
本 Pipe 包含 **1 个示例自定义工具**,展示如何使用 GitHub Copilot SDK 的工具调用功能。
---
## 🚀 Quick Start / 快速开始
### 1. Enable Tools / 启用工具
In Valves configuration:
在 Valves 配置中:
```
ENABLE_TOOLS: true
AVAILABLE_TOOLS: all
```
### 2. Test with Conversations / 测试对话
Try these examples:
尝试这些示例:
**English:**
- "Give me a random number between 1 and 100"
**中文:**
- "给我一个 1 到 100 之间的随机数"
---
## 📦 Included Tools / 内置工具
### 1. `generate_random_number` / 生成随机数
**Description:** Generate a random integer
**描述:** 生成随机整数
**Parameters / 参数:**
- `min` (optional): Minimum value (default: 1)
- `max` (optional): Maximum value (default: 100)
- `min` (可选): 最小值 (默认: 1)
- `max` (可选): 最大值 (默认: 100)
**Example / 示例:**
```
User: "Give me a random number between 1 and 10"
Copilot: [calls generate_random_number with min=1, max=10] "Generated random number: 7"
用户: "给我一个 1 到 10 之间的随机数"
Copilot: [调用 generate_random_number参数 min=1, max=10] "生成的随机数: 7"
```
---
## ⚙️ Advanced Configuration / 高级配置
### Select Specific Tools / 选择特定工具
Instead of enabling all tools, specify which ones to use:
不启用所有工具,而是指定要使用的工具:
```
ENABLE_TOOLS: true
AVAILABLE_TOOLS: generate_random_number
```
---
## 🔧 How Tool Calling Works / 工具调用的工作原理
```
1. User asks a question / 用户提问
2. Copilot decides if it needs a tool / Copilot 决定是否需要工具
3. If yes, Copilot calls the appropriate tool / 如果需要,调用相应工具
4. Tool executes and returns result / 工具执行并返回结果
5. Copilot uses the result to answer / Copilot 使用结果回答
```
### Visual Feedback / 可视化反馈
When tools are called, you'll see:
当工具被调用时,你会看到:
```
🔧 **Calling tool**: `generate_random_number`
✅ **Tool `generate_random_number` completed**
Generated random number: 7
```
---
## 📚 Creating Your Own Tools / 创建自定义工具
Want to add your own tools? Follow this pattern (module-level tools):
想要添加自己的工具?遵循这个模式(模块级工具):
```python
from pydantic import BaseModel, Field
from copilot import define_tool
class MyToolParams(BaseModel):
param_name: str = Field(description="Parameter description")
@define_tool(description="Clear description of what the tool does and when to use it")
async def my_tool(params: MyToolParams) -> str:
# Do something
result = do_something(params.param_name)
return f"Result: {result}"
```
Then register it in `_initialize_custom_tools()`:
然后将它添加到 `_initialize_custom_tools()`:
```python
def _initialize_custom_tools(self):
if not self.valves.ENABLE_TOOLS:
return []
all_tools = {
"generate_random_number": generate_random_number,
"my_tool": my_tool, # ✅ Add here
}
if self.valves.AVAILABLE_TOOLS == "all":
return list(all_tools.values())
enabled = [t.strip() for t in self.valves.AVAILABLE_TOOLS.split(",")]
return [all_tools[name] for name in enabled if name in all_tools]
```
---
## ⚠️ Important Notes / 重要说明
### Security / 安全性
- Tools run in the same process as the pipe
- Be careful with tools that execute code or access files
- Always validate input parameters
- 工具在与 Pipe 相同的进程中运行
- 谨慎处理执行代码或访问文件的工具
- 始终验证输入参数
### Performance / 性能
- Tool execution is synchronous during streaming
- Long-running tools may cause delays
- Consider adding timeouts for external API calls
- 工具执行在流式传输期间是同步的
- 长时间运行的工具可能导致延迟
- 考虑为外部 API 调用添加超时
### Debugging / 调试
- Enable `DEBUG: true` to see tool events in the browser console
- Check tool calls in `🔧 Calling tool` messages
- Tool errors are displayed in the response
- 启用 `DEBUG: true` 在浏览器控制台查看工具事件
-`🔧 Calling tool` 消息中检查工具调用
- 工具错误会显示在响应中
---
## 📖 References / 参考资料
- [Copilot SDK Documentation](https://github.com/github/copilot-sdk)
- [COPILOT_TOOLS_QUICKSTART.md](COPILOT_TOOLS_QUICKSTART.md) - Detailed implementation guide
- [JSON Schema](https://json-schema.org/) - For parameter definitions
---
**Version:** 0.2.3
**Last Updated:** 2026-01-27

View File

@@ -1,431 +0,0 @@
# GitHub Copilot SDK - Tool 功能实现指南
## 📋 概述
本指南介绍如何在 GitHub Copilot SDK Pipe 中实现 Function/Tool Calling 功能。
---
## 🏗️ 架构设计
### 工作流程
```
OpenWebUI Tools/Functions
↓ (转换)
Copilot SDK Tool Definition
↓ (注册)
Session Tool Handlers
↓ (调用)
Tool Execution → Result
↓ (返回)
Continue Conversation
```
### 核心接口
#### 1. Tool Definition工具定义
```python
from copilot.types import Tool
tool = Tool(
name="get_weather",
description="Get current weather for a location",
parameters={
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "City name, e.g., 'San Francisco'"
},
"unit": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature unit"
}
},
"required": ["location"]
},
handler=weather_handler # 处理函数
)
```
#### 2. Tool Handler处理函数
```python
from copilot.types import ToolInvocation, ToolResult
async def weather_handler(invocation: ToolInvocation) -> ToolResult:
"""
invocation 包含:
- session_id: str
- tool_call_id: str
- tool_name: str
- arguments: dict # {"location": "San Francisco", "unit": "celsius"}
"""
location = invocation["arguments"]["location"]
# 执行实际逻辑
weather_data = await fetch_weather(location)
# 返回结果
return ToolResult(
textResultForLlm=f"Weather in {location}: {weather_data['temp']}°C, {weather_data['condition']}",
resultType="success", # or "failure"
error=None,
toolTelemetry={"execution_time_ms": 150}
)
```
#### 3. Session Configuration会话配置
```python
from copilot.types import SessionConfig
session_config = SessionConfig(
model="claude-sonnet-4.5",
tools=[tool1, tool2, tool3], # ✅ 传递工具列表
available_tools=["get_weather", "search_web"], # 可选:过滤可用工具
excluded_tools=["dangerous_tool"], # 可选:排除工具
)
session = await client.create_session(config=session_config)
```
---
## 💻 实现方案
### 方案 A桥接 OpenWebUI Tools推荐
#### 1. 添加 Valves 配置
```python
class Valves(BaseModel):
ENABLE_TOOLS: bool = Field(
default=True,
description="Enable OpenWebUI tool integration"
)
TOOL_TIMEOUT: int = Field(
default=30,
description="Tool execution timeout (seconds)"
)
AVAILABLE_TOOLS: str = Field(
default="",
description="Filter specific tools (comma separated, empty = all)"
)
```
#### 2. 实现 Tool 转换器
```python
def _convert_openwebui_tools_to_copilot(
self,
owui_tools: List[dict],
__event_call__=None
) -> List[dict]:
"""
将 OpenWebUI tools 转换为 Copilot SDK 格式
OpenWebUI Tool 格式:
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather info",
"parameters": {...} # JSON Schema
}
}
"""
copilot_tools = []
for tool in owui_tools:
if tool.get("type") != "function":
continue
func = tool.get("function", {})
tool_name = func.get("name")
if not tool_name:
continue
# 应用过滤器
if self.valves.AVAILABLE_TOOLS:
allowed = [t.strip() for t in self.valves.AVAILABLE_TOOLS.split(",")]
if tool_name not in allowed:
continue
copilot_tools.append({
"name": tool_name,
"description": func.get("description", ""),
"parameters": func.get("parameters", {}),
"handler": self._create_tool_handler(tool_name, __event_call__)
})
self._emit_debug_log_sync(
f"Registered tool: {tool_name}",
__event_call__
)
return copilot_tools
```
#### 3. 实现动态 Tool Handler
```python
def _create_tool_handler(self, tool_name: str, __event_call__=None):
"""为每个 tool 创建 handler 函数"""
async def handler(invocation: dict) -> dict:
"""
Tool handler 实现
invocation 结构:
{
"session_id": "...",
"tool_call_id": "...",
"tool_name": "get_weather",
"arguments": {"location": "Beijing"}
}
"""
try:
self._emit_debug_log_sync(
f"Tool called: {invocation['tool_name']} with {invocation['arguments']}",
__event_call__
)
# 方法 1: 调用 OpenWebUI 内部 Function API
result = await self._execute_openwebui_function(
function_name=invocation["tool_name"],
arguments=invocation["arguments"]
)
# 方法 2: 通过 __event_emitter__ 触发(需要测试)
# 方法 3: 直接实现工具逻辑
return {
"textResultForLlm": str(result),
"resultType": "success",
"error": None,
"toolTelemetry": {}
}
except asyncio.TimeoutError:
return {
"textResultForLlm": "Tool execution timed out.",
"resultType": "failure",
"error": "timeout",
"toolTelemetry": {}
}
except Exception as e:
self._emit_debug_log_sync(
f"Tool error: {e}",
__event_call__
)
return {
"textResultForLlm": f"Tool execution failed: {str(e)}",
"resultType": "failure",
"error": str(e),
"toolTelemetry": {}
}
return handler
```
#### 4. 集成到 pipe() 方法
```python
async def pipe(
self,
body: dict,
__metadata__: Optional[dict] = None,
__event_emitter__=None,
__event_call__=None,
) -> Union[str, AsyncGenerator]:
# ... 现有代码 ...
# ✅ 提取并转换 tools
copilot_tools = []
if self.valves.ENABLE_TOOLS and body.get("tools"):
copilot_tools = self._convert_openwebui_tools_to_copilot(
body["tools"],
__event_call__
)
await self._emit_debug_log(
f"Enabled {len(copilot_tools)} tools",
__event_call__
)
# ✅ 传递给 SessionConfig
session_config = SessionConfig(
session_id=chat_id if chat_id else None,
model=real_model_id,
streaming=body.get("stream", False),
tools=copilot_tools, # ✅ 关键
infinite_sessions=infinite_session_config,
)
session = await client.create_session(config=session_config)
# ...
```
#### 5. 处理 Tool 调用事件
```python
def stream_response(...):
def handler(event):
event_type = str(event.type)
# ✅ Tool 调用开始
if "tool_invocation_started" in event_type:
tool_name = get_event_data(event, "tool_name", "")
yield f"\n🔧 **Calling tool**: `{tool_name}`\n"
# ✅ Tool 调用完成
elif "tool_invocation_completed" in event_type:
tool_name = get_event_data(event, "tool_name", "")
result = get_event_data(event, "result", "")
yield f"\n✅ **Tool result**: {result}\n"
# ✅ Tool 调用失败
elif "tool_invocation_failed" in event_type:
tool_name = get_event_data(event, "tool_name", "")
error = get_event_data(event, "error", "")
yield f"\n❌ **Tool failed**: `{tool_name}` - {error}\n"
```
---
### 方案 B自定义 Tool 实现
#### Valves 配置
```python
class Valves(BaseModel):
CUSTOM_TOOLS: str = Field(
default="[]",
description="Custom tools JSON: [{name, description, parameters, implementation}]"
)
```
#### 工具定义示例
```json
[
{
"name": "calculate",
"description": "Perform mathematical calculations",
"parameters": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "Math expression, e.g., '2 + 2 * 3'"
}
},
"required": ["expression"]
},
"implementation": "eval" // 或指定 Python 函数名
}
]
```
---
## 🧪 测试方案
### 1. 测试 Tool 定义
```python
# 在 OpenWebUI 中创建一个简单的 Function:
# Name: get_time
# Description: Get current time
# Parameters: {"type": "object", "properties": {}}
# 测试对话:
# User: "What time is it?"
# Expected: Copilot 调用 get_time tool返回当前时间
```
### 2. 测试 Tool 调用链
```python
# User: "Search for Python tutorials and summarize the top 3 results"
# Expected Flow:
# 1. Copilot calls search_web(query="Python tutorials")
# 2. Copilot receives search results
# 3. Copilot summarizes top 3
# 4. Returns final answer
```
### 3. 测试错误处理
```python
# User: "Call a non-existent tool"
# Expected: 返回 "Tool not supported" error
```
---
## 📊 事件监听
Tool 相关事件类型:
- `tool_invocation_started` - Tool 调用开始
- `tool_invocation_completed` - Tool 完成
- `tool_invocation_failed` - Tool 失败
- `tool_parameter_validation_failed` - 参数验证失败
---
## ⚠️ 注意事项
### 1. 安全性
- ✅ 验证 tool parameters
- ✅ 限制执行超时
- ✅ 不暴露详细错误信息给 LLM
- ❌ 禁止执行危险命令(如 `rm -rf`
### 2. 性能
- ⏱️ 设置合理的 timeout
- 🔄 考虑异步执行长时间运行的 tool
- 📊 记录 tool 执行时间toolTelemetry
### 3. 调试
- 🐛 在 DEBUG 模式下记录所有 tool 调用
- 📝 记录 arguments 和 results
- 🔍 使用前端 console 显示 tool 流程
---
## 🔗 参考资源
- [GitHub Copilot SDK 官方文档](https://github.com/github/copilot-sdk)
- [OpenWebUI Function API](https://docs.openwebui.com/features/plugin-system)
- [JSON Schema 规范](https://json-schema.org/)
---
## 📝 实现清单
- [ ] 添加 ENABLE_TOOLS Valve
- [ ] 实现 _convert_openwebui_tools_to_copilot()
- [ ] 实现 _create_tool_handler()
- [ ] 修改 SessionConfig 传递 tools
- [ ] 处理 tool 事件流
- [ ] 添加调试日志
- [ ] 测试基础 tool 调用
- [ ] 测试错误处理
- [ ] 更新文档和 README
- [ ] 同步中文版本
---
**作者:** Fu-Jie
**版本:** v1.0
**日期:** 2026-01-26

View File

@@ -1,835 +0,0 @@
# GitHub Copilot SDK Integration Workflow
**Author:** Fu-Jie
**Version:** 0.2.3
**Last Updated:** 2026-01-27
---
## Table of Contents
1. [Architecture Overview](#architecture-overview)
2. [Request Processing Flow](#request-processing-flow)
3. [Session Management](#session-management)
4. [Streaming Response Handling](#streaming-response-handling)
5. [Event Processing Mechanism](#event-processing-mechanism)
6. [Tool Execution Flow](#tool-execution-flow)
7. [System Prompt Extraction](#system-prompt-extraction)
8. [Configuration Parameters](#configuration-parameters)
9. [Key Functions Reference](#key-functions-reference)
---
## Architecture Overview
### Component Diagram
```
┌─────────────────────────────────────────────────────────────┐
│ OpenWebUI │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Pipe Interface (Entry Point) │ │
│ └─────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ _pipe_impl (Main Logic) │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ 1. Environment Setup (_setup_env) │ │ │
│ │ │ 2. Model Selection (request_model parsing) │ │ │
│ │ │ 3. Chat Context Extraction │ │ │
│ │ │ 4. System Prompt Extraction │ │ │
│ │ │ 5. Session Management (create/resume) │ │ │
│ │ │ 6. Streaming/Non-streaming Response │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └─────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ GitHub Copilot Client │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ • CopilotClient (SDK instance) │ │ │
│ │ │ • Session (conversation context) │ │ │
│ │ │ • Event Stream (async events) │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └─────────────────────┬─────────────────────────────────┘ │
│ │ │
└────────────────────────┼─────────────────────────────────────┘
┌──────────────────────┐
│ Copilot CLI Process │
│ (Backend Agent) │
└──────────────────────┘
```
### Key Components
1. **Pipe Interface**: OpenWebUI's standard entry point
2. **Environment Manager**: CLI setup, token validation, environment variables
3. **Session Manager**: Persistent conversation state with automatic compaction
4. **Event Processor**: Asynchronous streaming event handler
5. **Tool System**: Custom tool registration and execution
6. **Debug Logger**: Frontend console logging for troubleshooting
---
## Request Processing Flow
### Complete Request Lifecycle
```mermaid
graph TD
A[OpenWebUI Request] --> B[pipe Entry Point]
B --> C[_pipe_impl]
C --> D{Setup Environment}
D --> E[Parse Model ID]
E --> F[Extract Chat Context]
F --> G[Extract System Prompt]
G --> H{Session Exists?}
H -->|Yes| I[Resume Session]
H -->|No| J[Create New Session]
I --> K[Initialize Tools]
J --> K
K --> L[Process Images]
L --> M{Streaming Mode?}
M -->|Yes| N[stream_response]
M -->|No| O[send_and_wait]
N --> P[Async Event Stream]
O --> Q[Direct Response]
P --> R[Return to OpenWebUI]
Q --> R
```
### Step-by-Step Breakdown
#### 1. Environment Setup (`_setup_env`)
```python
def _setup_env(self, __event_call__=None):
"""
Priority:
1. Check VALVES.CLI_PATH
2. Search system PATH
3. Auto-install via curl (if not found)
4. Set GH_TOKEN environment variables
"""
```
**Actions:**
- Locate Copilot CLI binary
- Set `COPILOT_CLI_PATH` environment variable
- Configure `GH_TOKEN` for authentication
- Apply custom environment variables
#### 2. Model Selection
```python
# Input: body["model"] = "copilotsdk-claude-sonnet-4.5"
request_model = body.get("model", "")
if request_model.startswith(f"{self.id}-"):
real_model_id = request_model[len(f"{self.id}-"):] # "claude-sonnet-4.5"
```
#### 3. Chat Context Extraction (`_get_chat_context`)
```python
# Priority order for chat_id:
# 1. __metadata__ (most reliable)
# 2. body["chat_id"]
# 3. body["metadata"]["chat_id"]
chat_ctx = self._get_chat_context(body, __metadata__, __event_call__)
chat_id = chat_ctx.get("chat_id")
```
#### 4. System Prompt Extraction (`_extract_system_prompt`)
Multi-source fallback strategy:
1. `metadata.model.params.system`
2. Model database lookup (by model_id)
3. `body.params.system`
4. Messages with `role="system"`
#### 5. Session Creation/Resumption
**New Session:**
```python
session_config = SessionConfig(
session_id=chat_id,
model=real_model_id,
streaming=is_streaming,
tools=custom_tools,
system_message={"mode": "append", "content": system_prompt_content},
infinite_sessions=InfiniteSessionConfig(
enabled=True,
background_compaction_threshold=0.8,
buffer_exhaustion_threshold=0.95
)
)
session = await client.create_session(config=session_config)
```
**Resume Session:**
```python
try:
session = await client.resume_session(chat_id)
# Session state preserved: history, tools, workspace
except Exception:
# Fallback to creating new session
```
---
## Session Management
### Infinite Sessions Architecture
```
┌─────────────────────────────────────────────────────────┐
│ Session Lifecycle │
│ │
│ ┌──────────┐ create ┌──────────┐ resume ┌───────┴───┐
│ │ Chat ID │─────────▶ │ Session │ ◀────────│ OpenWebUI │
│ └──────────┘ │ State │ └───────────┘
│ └─────┬────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Context Window Management │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Messages [user, assistant, tool_results...] │ │ │
│ │ │ Token Usage: ████████████░░░░ (80%) │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Threshold Reached (0.8) │ │ │
│ │ │ → Background Compaction Triggered │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Compacted Summary + Recent Messages │ │ │
│ │ │ Token Usage: ██████░░░░░░░░░░░ (40%) │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
### Configuration Parameters
```python
InfiniteSessionConfig(
enabled=True, # Enable infinite sessions
background_compaction_threshold=0.8, # Start compaction at 80% token usage
buffer_exhaustion_threshold=0.95 # Emergency threshold at 95%
)
```
**Behavior:**
- **< 80%**: Normal operation, no compaction
- **80-95%**: Background compaction (summarize older messages)
- **> 95%**: Force compaction before next request
---
## Streaming Response Handling
### Event-Driven Architecture
```python
async def stream_response(
self, client, session, send_payload, init_message: str = "", __event_call__=None
) -> AsyncGenerator:
"""
Asynchronous event processing with queue-based buffering.
Flow:
1. Start async send task
2. Register event handler
3. Process events via queue
4. Yield chunks to OpenWebUI
5. Clean up resources
"""
```
### Event Processing Pipeline
```
┌────────────────────────────────────────────────────────────┐
│ Copilot SDK Event Stream │
└────────────────────┬───────────────────────────────────────┘
┌────────────────────────┐
│ Event Handler │
│ (Sync Callback) │
└────────┬───────────────┘
┌────────────────────────┐
│ Async Queue │
│ (Thread-safe) │
└────────┬───────────────┘
┌────────────────────────┐
│ Consumer Loop │
│ (async for) │
└────────┬───────────────┘
┌────────────────────────┐
│ yield to OpenWebUI │
└────────────────────────┘
```
### State Management During Streaming
```python
state = {
"thinking_started": False, # <think> tags opened
"content_sent": False # Main content has started
}
active_tools = {} # Track concurrent tool executions
```
**State Transitions:**
1. `reasoning_delta` arrives → `thinking_started = True` → Output: `<think>\n{reasoning}`
2. `message_delta` arrives → Close `</think>` if open → `content_sent = True` → Output: `{content}`
3. `tool.execution_start` → Output tool indicator (inside/outside `<think>`)
4. `session.complete` → Finalize stream
---
## Event Processing Mechanism
### Event Type Reference
Following official SDK patterns (from `copilot.SessionEventType`):
| Event Type | Description | Key Data Fields | Handler Action |
|-----------|-------------|-----------------|----------------|
| `assistant.message_delta` | Main content streaming | `delta_content` | Yield text chunk |
| `assistant.reasoning_delta` | Chain-of-thought | `delta_content` | Wrap in `<think>` tags |
| `tool.execution_start` | Tool call initiated | `name`, `tool_call_id` | Display tool indicator |
| `tool.execution_complete` | Tool finished | `result.content` | Show completion status |
| `session.compaction_start` | Context compaction begins | - | Log debug info |
| `session.compaction_complete` | Compaction done | - | Log debug info |
| `session.error` | Error occurred | `error`, `message` | Emit error notification |
### Event Handler Implementation
```python
def handler(event):
"""Process streaming events following official SDK patterns."""
event_type = get_event_type(event) # Handle enum/string types
# Extract data using safe_get_data_attr (handles dict/object)
if event_type == "assistant.message_delta":
delta = safe_get_data_attr(event, "delta_content")
if delta:
queue.put_nowait(delta) # Thread-safe enqueue
```
### Official SDK Pattern Compliance
```python
def safe_get_data_attr(event, attr: str, default=None):
"""
Official pattern: event.data.delta_content
Handles both dict and object access patterns.
"""
if not hasattr(event, "data") or event.data is None:
return default
data = event.data
# Dict access (JSON-like)
if isinstance(data, dict):
return data.get(attr, default)
# Object attribute (Python SDK)
return getattr(data, attr, default)
```
---
## Tool Execution Flow
### Tool Registration
```python
# 1. Define tool at module level
@define_tool(description="Generate a random integer within a specified range.")
async def generate_random_number(params: RandomNumberParams) -> str:
number = random.randint(params.min, params.max)
return f"Generated random number: {number}"
# 2. Register in _initialize_custom_tools
def _initialize_custom_tools(self):
if not self.valves.ENABLE_TOOLS:
return []
all_tools = {
"generate_random_number": generate_random_number,
}
# Filter based on AVAILABLE_TOOLS valve
if self.valves.AVAILABLE_TOOLS == "all":
return list(all_tools.values())
enabled = [t.strip() for t in self.valves.AVAILABLE_TOOLS.split(",")]
return [all_tools[name] for name in enabled if name in all_tools]
```
### Tool Execution Timeline
```
User Message: "Generate a random number between 1 and 100"
Model Decision: Use tool `generate_random_number`
Event: tool.execution_start
│ → Display: "🔧 Running Tool: generate_random_number"
Tool Function Execution (async)
Event: tool.execution_complete
│ → Result: "Generated random number: 42"
│ → Display: "✅ Tool Completed: 42"
Model generates response using tool result
Event: assistant.message_delta
│ → "I generated the number 42 for you."
Stream Complete
```
### Visual Indicators
**Before Content:**
```markdown
<think>
Running Tool: generate_random_number...
Tool `generate_random_number` Completed. Result: 42
</think>
I generated the number 42 for you.
```
**After Content Started:**
```markdown
The number is
> 🔧 **Running Tool**: `generate_random_number`
> ✅ **Tool Completed**: 42
actually 42.
```
---
## System Prompt Extraction
### Multi-Source Priority System
```python
async def _extract_system_prompt(self, body, messages, request_model, real_model_id):
"""
Priority order:
1. metadata.model.params.system (highest)
2. Model database lookup
3. body.params.system
4. messages[role="system"] (fallback)
"""
```
### Source 1: Metadata Model Params
```python
# OpenWebUI injects model configuration
metadata = body.get("metadata", {})
meta_model = metadata.get("model", {})
meta_params = meta_model.get("params", {})
system_prompt = meta_params.get("system") # Priority 1
```
### Source 2: Model Database
```python
from open_webui.models.models import Models
# Try multiple model ID variations
model_ids_to_try = [
request_model, # "copilotsdk-claude-sonnet-4.5"
request_model.removeprefix(...), # "claude-sonnet-4.5"
real_model_id, # From valves
]
for mid in model_ids_to_try:
model_record = Models.get_model_by_id(mid)
if model_record and hasattr(model_record, "params"):
system_prompt = model_record.params.get("system")
if system_prompt:
break
```
### Source 3: Body Params
```python
body_params = body.get("params", {})
system_prompt = body_params.get("system")
```
### Source 4: System Message
```python
for msg in messages:
if msg.get("role") == "system":
system_prompt = self._extract_text_from_content(msg.get("content"))
break
```
### Configuration in SessionConfig
```python
system_message_config = {
"mode": "append", # Append to conversation context
"content": system_prompt_content
}
session_config = SessionConfig(
system_message=system_message_config,
# ... other params
)
```
---
## Configuration Parameters
### Valve Definitions
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `GH_TOKEN` | str | `""` | GitHub Fine-grained Token (requires 'Copilot Requests' permission) |
| `MODEL_ID` | str | `"claude-sonnet-4.5"` | Default model when dynamic fetching fails |
| `CLI_PATH` | str | `"/usr/local/bin/copilot"` | Path to Copilot CLI binary |
| `DEBUG` | bool | `False` | Enable frontend console debug logging |
| `LOG_LEVEL` | str | `"error"` | CLI log level: none, error, warning, info, debug, all |
| `SHOW_THINKING` | bool | `True` | Display model reasoning in `<think>` tags |
| `SHOW_WORKSPACE_INFO` | bool | `True` | Show session workspace path in debug mode |
| `EXCLUDE_KEYWORDS` | str | `""` | Comma-separated keywords to exclude models |
| `WORKSPACE_DIR` | str | `""` | Restricted workspace directory (empty = process cwd) |
| `INFINITE_SESSION` | bool | `True` | Enable automatic context compaction |
| `COMPACTION_THRESHOLD` | float | `0.8` | Background compaction at 80% token usage |
| `BUFFER_THRESHOLD` | float | `0.95` | Emergency threshold at 95% |
| `TIMEOUT` | int | `300` | Stream chunk timeout (seconds) |
| `CUSTOM_ENV_VARS` | str | `""` | JSON string of custom environment variables |
| `ENABLE_TOOLS` | bool | `False` | Enable custom tool system |
| `AVAILABLE_TOOLS` | str | `"all"` | Available tools: "all" or comma-separated list |
### Environment Variables
```bash
# Set by _setup_env
export COPILOT_CLI_PATH="/usr/local/bin/copilot"
export GH_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxx"
export GITHUB_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxx"
# Custom variables (from CUSTOM_ENV_VARS valve)
export CUSTOM_VAR_1="value1"
export CUSTOM_VAR_2="value2"
```
---
## Key Functions Reference
### Entry Points
#### `pipe(body, __metadata__, __event_emitter__, __event_call__)`
- **Purpose**: OpenWebUI stable entry point
- **Returns**: Delegates to `_pipe_impl`
#### `_pipe_impl(body, __metadata__, __event_emitter__, __event_call__)`
- **Purpose**: Main request processing logic
- **Flow**: Setup → Extract → Session → Response
- **Returns**: `str` (non-streaming) or `AsyncGenerator` (streaming)
#### `pipes()`
- **Purpose**: Dynamic model list fetching
- **Returns**: List of available models with multiplier info
- **Caching**: Uses `_model_cache` to avoid repeated API calls
### Session Management
#### `_build_session_config(chat_id, real_model_id, custom_tools, system_prompt_content, is_streaming)`
- **Purpose**: Construct SessionConfig object
- **Returns**: `SessionConfig` with infinite sessions and tools
#### `_get_chat_context(body, __metadata__, __event_call__)`
- **Purpose**: Extract chat_id with priority fallback
- **Returns**: `{"chat_id": str}`
### Streaming
#### `stream_response(client, session, send_payload, init_message, __event_call__)`
- **Purpose**: Async streaming event processor
- **Yields**: Text chunks to OpenWebUI
- **Resources**: Auto-cleanup client and session
#### `handler(event)`
- **Purpose**: Sync event callback (inside `stream_response`)
- **Action**: Parse event → Enqueue chunks → Update state
### Helpers
#### `_emit_debug_log(message, __event_call__)`
- **Purpose**: Send debug logs to frontend console
- **Condition**: Only when `DEBUG=True`
#### `_setup_env(__event_call__)`
- **Purpose**: Locate CLI, set environment variables
- **Side Effects**: Modifies `os.environ`
#### `_extract_system_prompt(body, messages, request_model, real_model_id, __event_call__)`
- **Purpose**: Multi-source system prompt extraction
- **Returns**: `(system_prompt_content, source_name)`
#### `_process_images(messages, __event_call__)`
- **Purpose**: Extract text and images from multimodal messages
- **Returns**: `(text_content, attachments_list)`
#### `_initialize_custom_tools()`
- **Purpose**: Register and filter custom tools
- **Returns**: List of tool functions
### Utility Functions
#### `get_event_type(event) -> str`
- **Purpose**: Extract event type string from enum/string
- **Handles**: `SessionEventType` enum → `.value` extraction
#### `safe_get_data_attr(event, attr: str, default=None)`
- **Purpose**: Safe attribute extraction from event.data
- **Handles**: Both dict access and object attribute access
---
## Troubleshooting Guide
### Enable Debug Mode
```python
# In OpenWebUI Valves UI:
DEBUG = True
SHOW_WORKSPACE_INFO = True
LOG_LEVEL = "debug"
```
### Debug Output Location
**Frontend Console:**
```javascript
// Open browser DevTools (F12)
// Look for logs with prefix: [Copilot Pipe]
console.debug("[Copilot Pipe] Extracted ChatID: abc123 (Source: __metadata__)")
```
**Backend Logs:**
```python
# Python logging output
logger.debug(f"[Copilot Pipe] Session resumed: {chat_id}")
```
### Common Issues
#### 1. Session Not Resuming
**Symptom:** New session created every request
**Causes:**
- `chat_id` not extracted correctly
- Session expired on Copilot side
- `INFINITE_SESSION=False` (sessions not persistent)
**Solution:**
```python
# Check debug logs for:
"Extracted ChatID: <id> (Source: ...)"
"Session <id> not found (...), creating new."
```
#### 2. System Prompt Not Applied
**Symptom:** Model ignores configured system prompt
**Causes:**
- Not found in any of 4 sources
- Session resumed (system prompt only set on creation)
**Solution:**
```python
# Check debug logs for:
"Extracted system prompt from <source> (length: X)"
"Configured system message (mode: append)"
```
#### 3. Tools Not Available
**Symptom:** Model can't use custom tools
**Causes:**
- `ENABLE_TOOLS=False`
- Tool not registered in `_initialize_custom_tools`
- Wrong `AVAILABLE_TOOLS` filter
**Solution:**
```python
# Check debug logs for:
"Enabled X custom tools: ['tool1', 'tool2']"
```
---
## Performance Optimization
### Model List Caching
```python
# First request: Fetch from API
models = await client.list_models()
self._model_cache = [...] # Cache result
# Subsequent requests: Use cache
if self._model_cache:
return self._model_cache
```
### Session Persistence
**Impact:** Eliminates redundant model initialization on every request
```python
# Without session:
# Each request: Initialize model → Load context → Generate → Discard
# With session (chat_id):
# First request: Initialize model → Load context → Generate → Save
# Later: Resume → Generate (instant)
```
### Streaming vs Non-streaming
**Streaming:**
- Lower perceived latency (first token faster)
- Better UX for long responses
- Resource cleanup via generator exit
**Non-streaming:**
- Simpler error handling
- Atomic response (no partial output)
- Use for short responses
---
## Security Considerations
### Token Protection
```python
# ❌ Never log tokens
logger.debug(f"Token: {self.valves.GH_TOKEN}") # DON'T DO THIS
# ✅ Mask sensitive data
logger.debug(f"Token configured: {'*' * 10}")
```
### Workspace Isolation
```python
# Set WORKSPACE_DIR to restrict file access
WORKSPACE_DIR = "/safe/sandbox/path"
# Copilot CLI respects this directory
client_config["cwd"] = WORKSPACE_DIR
```
### Input Validation
```python
# Validate chat_id format
if chat_id and not re.match(r'^[a-zA-Z0-9_-]+$', chat_id):
logger.warning(f"Invalid chat_id format: {chat_id}")
chat_id = None
```
---
## Future Enhancements
### Planned Features
1. **Multi-Session Management**: Support multiple parallel sessions per user
2. **Session Analytics**: Track token usage, compaction frequency
3. **Tool Result Caching**: Avoid redundant tool calls
4. **Custom Event Filters**: User-configurable event handling
5. **Workspace Templates**: Pre-configured workspace environments
6. **Streaming Abort**: Graceful cancellation of long-running requests
### API Evolution
Monitoring Copilot SDK updates for:
- New event types (e.g., `assistant.function_call`)
- Enhanced tool capabilities
- Improved session serialization
---
## References
- [GitHub Copilot SDK Documentation](https://github.com/github/copilot-sdk)
- [OpenWebUI Pipe Development](https://docs.openwebui.com/)
- [OpenWebUI Extensions Project](https://github.com/Fu-Jie/openwebui-extensions)
---
**License:** MIT
**Maintainer:** Fu-Jie ([@Fu-Jie](https://github.com/Fu-Jie))

View File

@@ -1,835 +0,0 @@
# GitHub Copilot SDK 集成工作流程
**作者:** Fu-Jie
**版本:** 0.2.3
**最后更新:** 2026-01-27
---
## 目录
1. [架构概览](#架构概览)
2. [请求处理流程](#请求处理流程)
3. [会话管理](#会话管理)
4. [流式响应处理](#流式响应处理)
5. [事件处理机制](#事件处理机制)
6. [工具执行流程](#工具执行流程)
7. [系统提示词提取](#系统提示词提取)
8. [配置参数](#配置参数)
9. [核心函数参考](#核心函数参考)
---
## 架构概览
### 组件图
```
┌─────────────────────────────────────────────────────────────┐
│ OpenWebUI │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Pipe 接口 (入口点) │ │
│ └─────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ _pipe_impl (主逻辑) │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ 1. 环境设置 (_setup_env) │ │ │
│ │ │ 2. 模型选择 (request_model 解析) │ │ │
│ │ │ 3. 聊天上下文提取 │ │ │
│ │ │ 4. 系统提示词提取 │ │ │
│ │ │ 5. 会话管理 (创建/恢复) │ │ │
│ │ │ 6. 流式/非流式响应 │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └─────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ GitHub Copilot 客户端 │ │
│ │ ┌──────────────────────────────────────────────────┐ │ │
│ │ │ • CopilotClient (SDK 实例) │ │ │
│ │ │ • Session (对话上下文) │ │ │
│ │ │ • Event Stream (异步事件流) │ │ │
│ │ └──────────────────────────────────────────────────┘ │ │
│ └─────────────────────┬─────────────────────────────────┘ │
│ │ │
└────────────────────────┼─────────────────────────────────────┘
┌──────────────────────┐
│ Copilot CLI 进程 │
│ (后端代理) │
└──────────────────────┘
```
### 核心组件
1. **Pipe 接口**OpenWebUI 的标准入口点
2. **环境管理器**CLI 设置、令牌验证、环境变量
3. **会话管理器**:持久化对话状态,自动压缩
4. **事件处理器**:异步流式事件处理器
5. **工具系统**:自定义工具注册和执行
6. **调试日志器**:前端控制台日志,用于故障排除
---
## 请求处理流程
### 完整请求生命周期
```mermaid
graph TD
A[OpenWebUI 请求] --> B[pipe 入口点]
B --> C[_pipe_impl]
C --> D{设置环境}
D --> E[解析模型 ID]
E --> F[提取聊天上下文]
F --> G[提取系统提示词]
G --> H{会话存在?}
H -->|是| I[恢复会话]
H -->|否| J[创建新会话]
I --> K[初始化工具]
J --> K
K --> L[处理图片]
L --> M{流式模式?}
M -->|是| N[stream_response]
M -->|否| O[send_and_wait]
N --> P[异步事件流]
O --> Q[直接响应]
P --> R[返回到 OpenWebUI]
Q --> R
```
### 逐步分解
#### 1. 环境设置 (`_setup_env`)
```python
def _setup_env(self, __event_call__=None):
"""
优先级:
1. 检查 VALVES.CLI_PATH
2. 搜索系统 PATH
3. 自动通过 curl 安装(如果未找到)
4. 设置 GH_TOKEN 环境变量
"""
```
**操作:**
- 定位 Copilot CLI 二进制文件
- 设置 `COPILOT_CLI_PATH` 环境变量
- 配置 `GH_TOKEN` 进行身份验证
- 应用自定义环境变量
#### 2. 模型选择
```python
# 输入body["model"] = "copilotsdk-claude-sonnet-4.5"
request_model = body.get("model", "")
if request_model.startswith(f"{self.id}-"):
real_model_id = request_model[len(f"{self.id}-"):] # "claude-sonnet-4.5"
```
#### 3. 聊天上下文提取 (`_get_chat_context`)
```python
# chat_id 的优先级顺序:
# 1. __metadata__最可靠
# 2. body["chat_id"]
# 3. body["metadata"]["chat_id"]
chat_ctx = self._get_chat_context(body, __metadata__, __event_call__)
chat_id = chat_ctx.get("chat_id")
```
#### 4. 系统提示词提取 (`_extract_system_prompt`)
多源回退策略:
1. `metadata.model.params.system`
2. 模型数据库查询(按 model_id
3. `body.params.system`
4. 包含 `role="system"` 的消息
#### 5. 会话创建/恢复
**新会话:**
```python
session_config = SessionConfig(
session_id=chat_id,
model=real_model_id,
streaming=is_streaming,
tools=custom_tools,
system_message={"mode": "append", "content": system_prompt_content},
infinite_sessions=InfiniteSessionConfig(
enabled=True,
background_compaction_threshold=0.8,
buffer_exhaustion_threshold=0.95
)
)
session = await client.create_session(config=session_config)
```
**恢复会话:**
```python
try:
session = await client.resume_session(chat_id)
# 会话状态保留:历史、工具、工作区
except Exception:
# 回退到创建新会话
```
---
## 会话管理
### 无限会话架构
```
┌─────────────────────────────────────────────────────────┐
│ 会话生命周期 │
│ │
│ ┌──────────┐ 创建 ┌──────────┐ 恢复 ┌───────────┐ │
│ │ Chat ID │─────▶ │ Session │ ◀────────│ OpenWebUI │ │
│ └──────────┘ │ State │ └───────────┘ │
│ └─────┬────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ 上下文窗口管理 │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ 消息 [user, assistant, tool_results...] │ │ │
│ │ │ Token 使用率: ████████████░░░░ (80%) │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ 达到阈值 (0.8) │ │ │
│ │ │ → 后台压缩触发 │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ 压缩摘要 + 最近消息 │ │ │
│ │ │ Token 使用率: ██████░░░░░░░░░░░ (40%) │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
### 配置参数
```python
InfiniteSessionConfig(
enabled=True, # 启用无限会话
background_compaction_threshold=0.8, # 在 80% token 使用率时开始压缩
buffer_exhaustion_threshold=0.95 # 95% 紧急阈值
)
```
**行为:**
- **< 80%**:正常操作,无压缩
- **80-95%**:后台压缩(总结旧消息)
- **> 95%**:在下一个请求前强制压缩
---
## 流式响应处理
### 事件驱动架构
```python
async def stream_response(
self, client, session, send_payload, init_message: str = "", __event_call__=None
) -> AsyncGenerator:
"""
使用基于队列的缓冲进行异步事件处理。
流程:
1. 启动异步发送任务
2. 注册事件处理器
3. 通过队列处理事件
4. 向 OpenWebUI 产出块
5. 清理资源
"""
```
### 事件处理管道
```
┌────────────────────────────────────────────────────────────┐
│ Copilot SDK 事件流 │
└────────────────────┬───────────────────────────────────────┘
┌────────────────────────┐
│ 事件处理器 │
│ (同步回调) │
└────────┬───────────────┘
┌────────────────────────┐
│ 异步队列 │
│ (线程安全) │
└────────┬───────────────┘
┌────────────────────────┐
│ 消费者循环 │
│ (async for) │
└────────┬───────────────┘
┌────────────────────────┐
│ yield 到 OpenWebUI │
└────────────────────────┘
```
### 流式传输期间的状态管理
```python
state = {
"thinking_started": False, # <think> 标签已打开
"content_sent": False # 主内容已开始
}
active_tools = {} # 跟踪并发工具执行
```
**状态转换:**
1. `reasoning_delta` 到达 → `thinking_started = True` → 输出:`<think>\n{reasoning}`
2. `message_delta` 到达 → 如果打开则关闭 `</think>``content_sent = True` → 输出:`{content}`
3. `tool.execution_start` → 输出工具指示器(在 `<think>` 内部/外部)
4. `session.complete` → 完成流
---
## 事件处理机制
### 事件类型参考
遵循官方 SDK 模式(来自 `copilot.SessionEventType`
| 事件类型 | 描述 | 关键数据字段 | 处理器操作 |
|---------|------|-------------|-----------|
| `assistant.message_delta` | 主内容流式传输 | `delta_content` | 产出文本块 |
| `assistant.reasoning_delta` | 思维链 | `delta_content` | 用 `<think>` 标签包装 |
| `tool.execution_start` | 工具调用启动 | `name`, `tool_call_id` | 显示工具指示器 |
| `tool.execution_complete` | 工具完成 | `result.content` | 显示完成状态 |
| `session.compaction_start` | 上下文压缩开始 | - | 记录调试信息 |
| `session.compaction_complete` | 压缩完成 | - | 记录调试信息 |
| `session.error` | 发生错误 | `error`, `message` | 发出错误通知 |
### 事件处理器实现
```python
def handler(event):
"""遵循官方 SDK 模式处理流式事件。"""
event_type = get_event_type(event) # 处理枚举/字符串类型
# 使用 safe_get_data_attr 提取数据(处理 dict/object
if event_type == "assistant.message_delta":
delta = safe_get_data_attr(event, "delta_content")
if delta:
queue.put_nowait(delta) # 线程安全入队
```
### 官方 SDK 模式合规性
```python
def safe_get_data_attr(event, attr: str, default=None):
"""
官方模式event.data.delta_content
处理 dict 和对象访问模式。
"""
if not hasattr(event, "data") or event.data is None:
return default
data = event.data
# Dict 访问(类似 JSON
if isinstance(data, dict):
return data.get(attr, default)
# 对象属性Python SDK
return getattr(data, attr, default)
```
---
## 工具执行流程
### 工具注册
```python
# 1. 在模块级别定义工具
@define_tool(description="在指定范围内生成随机整数。")
async def generate_random_number(params: RandomNumberParams) -> str:
number = random.randint(params.min, params.max)
return f"生成的随机数: {number}"
# 2. 在 _initialize_custom_tools 中注册
def _initialize_custom_tools(self):
if not self.valves.ENABLE_TOOLS:
return []
all_tools = {
"generate_random_number": generate_random_number,
}
# 根据 AVAILABLE_TOOLS valve 过滤
if self.valves.AVAILABLE_TOOLS == "all":
return list(all_tools.values())
enabled = [t.strip() for t in self.valves.AVAILABLE_TOOLS.split(",")]
return [all_tools[name] for name in enabled if name in all_tools]
```
### 工具执行时间线
```
用户消息:生成一个 1 到 100 之间的随机数
模型决策:使用工具 `generate_random_number`
事件tool.execution_start
│ → 显示:"🔧 运行工具generate_random_number"
工具函数执行(异步)
事件tool.execution_complete
│ → 结果:"生成的随机数42"
│ → 显示:"✅ 工具完成42"
模型使用工具结果生成响应
事件assistant.message_delta
│ → "我为你生成了数字 42。"
流完成
```
### 视觉指示器
**内容前:**
```markdown
<think>
运行工具generate_random_number...
工具 `generate_random_number` 完成。结果42
</think>
我为你生成了数字 42。
```
**内容开始后:**
```markdown
数字是
> 🔧 **运行工具**`generate_random_number`
> ✅ **工具完成**42
实际上是 42。
```
---
## 系统提示词提取
### 多源优先级系统
```python
async def _extract_system_prompt(self, body, messages, request_model, real_model_id):
"""
优先级顺序:
1. metadata.model.params.system最高
2. 模型数据库查询
3. body.params.system
4. messages[role="system"](回退)
"""
```
### 来源 1元数据模型参数
```python
# OpenWebUI 注入模型配置
metadata = body.get("metadata", {})
meta_model = metadata.get("model", {})
meta_params = meta_model.get("params", {})
system_prompt = meta_params.get("system") # 优先级 1
```
### 来源 2模型数据库
```python
from open_webui.models.models import Models
# 尝试多个模型 ID 变体
model_ids_to_try = [
request_model, # "copilotsdk-claude-sonnet-4.5"
request_model.removeprefix(...), # "claude-sonnet-4.5"
real_model_id, # 来自 valves
]
for mid in model_ids_to_try:
model_record = Models.get_model_by_id(mid)
if model_record and hasattr(model_record, "params"):
system_prompt = model_record.params.get("system")
if system_prompt:
break
```
### 来源 3Body 参数
```python
body_params = body.get("params", {})
system_prompt = body_params.get("system")
```
### 来源 4系统消息
```python
for msg in messages:
if msg.get("role") == "system":
system_prompt = self._extract_text_from_content(msg.get("content"))
break
```
### SessionConfig 中的配置
```python
system_message_config = {
"mode": "append", # 追加到对话上下文
"content": system_prompt_content
}
session_config = SessionConfig(
system_message=system_message_config,
# ... 其他参数
)
```
---
## 配置参数
### Valve 定义
| 参数 | 类型 | 默认值 | 描述 |
|-----|------|--------|------|
| `GH_TOKEN` | str | `""` | GitHub 精细化令牌(需要 'Copilot Requests' 权限) |
| `MODEL_ID` | str | `"claude-sonnet-4.5"` | 动态获取失败时的默认模型 |
| `CLI_PATH` | str | `"/usr/local/bin/copilot"` | Copilot CLI 二进制文件路径 |
| `DEBUG` | bool | `False` | 启用前端控制台调试日志 |
| `LOG_LEVEL` | str | `"error"` | CLI 日志级别none、error、warning、info、debug、all |
| `SHOW_THINKING` | bool | `True` | 在 `<think>` 标签中显示模型推理 |
| `SHOW_WORKSPACE_INFO` | bool | `True` | 在调试模式下显示会话工作区路径 |
| `EXCLUDE_KEYWORDS` | str | `""` | 逗号分隔的关键字,用于排除模型 |
| `WORKSPACE_DIR` | str | `""` | 限制的工作区目录(空 = 进程 cwd |
| `INFINITE_SESSION` | bool | `True` | 启用自动上下文压缩 |
| `COMPACTION_THRESHOLD` | float | `0.8` | 80% token 使用率时后台压缩 |
| `BUFFER_THRESHOLD` | float | `0.95` | 95% 紧急阈值 |
| `TIMEOUT` | int | `300` | 流块超时(秒) |
| `CUSTOM_ENV_VARS` | str | `""` | 自定义环境变量的 JSON 字符串 |
| `ENABLE_TOOLS` | bool | `False` | 启用自定义工具系统 |
| `AVAILABLE_TOOLS` | str | `"all"` | 可用工具:"all" 或逗号分隔列表 |
### 环境变量
```bash
# 由 _setup_env 设置
export COPILOT_CLI_PATH="/usr/local/bin/copilot"
export GH_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxx"
export GITHUB_TOKEN="ghp_xxxxxxxxxxxxxxxxxxxx"
# 自定义变量(来自 CUSTOM_ENV_VARS valve
export CUSTOM_VAR_1="value1"
export CUSTOM_VAR_2="value2"
```
---
## 核心函数参考
### 入口点
#### `pipe(body, __metadata__, __event_emitter__, __event_call__)`
- **目的**OpenWebUI 稳定入口点
- **返回**:委托给 `_pipe_impl`
#### `_pipe_impl(body, __metadata__, __event_emitter__, __event_call__)`
- **目的**:主请求处理逻辑
- **流程**:设置 → 提取 → 会话 → 响应
- **返回**`str`(非流式)或 `AsyncGenerator`(流式)
#### `pipes()`
- **目的**:动态模型列表获取
- **返回**:带有倍数信息的可用模型列表
- **缓存**:使用 `_model_cache` 避免重复 API 调用
### 会话管理
#### `_build_session_config(chat_id, real_model_id, custom_tools, system_prompt_content, is_streaming)`
- **目的**:构建 SessionConfig 对象
- **返回**:带有无限会话和工具的 `SessionConfig`
#### `_get_chat_context(body, __metadata__, __event_call__)`
- **目的**:使用优先级回退提取 chat_id
- **返回**`{"chat_id": str}`
### 流式传输
#### `stream_response(client, session, send_payload, init_message, __event_call__)`
- **目的**:异步流式事件处理器
- **产出**:文本块到 OpenWebUI
- **资源**:自动清理客户端和会话
#### `handler(event)`
- **目的**:同步事件回调(在 `stream_response` 内)
- **操作**:解析事件 → 入队块 → 更新状态
### 辅助函数
#### `_emit_debug_log(message, __event_call__)`
- **目的**:将调试日志发送到前端控制台
- **条件**:仅当 `DEBUG=True`
#### `_setup_env(__event_call__)`
- **目的**:定位 CLI设置环境变量
- **副作用**:修改 `os.environ`
#### `_extract_system_prompt(body, messages, request_model, real_model_id, __event_call__)`
- **目的**:多源系统提示词提取
- **返回**`(system_prompt_content, source_name)`
#### `_process_images(messages, __event_call__)`
- **目的**:从多模态消息中提取文本和图片
- **返回**`(text_content, attachments_list)`
#### `_initialize_custom_tools()`
- **目的**:注册和过滤自定义工具
- **返回**:工具函数列表
### 实用函数
#### `get_event_type(event) -> str`
- **目的**:从枚举/字符串提取事件类型字符串
- **处理**`SessionEventType` 枚举 → `.value` 提取
#### `safe_get_data_attr(event, attr: str, default=None)`
- **目的**:从 event.data 安全提取属性
- **处理**dict 访问和对象属性访问
---
## 故障排除指南
### 启用调试模式
```python
# 在 OpenWebUI Valves UI 中:
DEBUG = True
SHOW_WORKSPACE_INFO = True
LOG_LEVEL = "debug"
```
### 调试输出位置
**前端控制台:**
```javascript
// 打开浏览器开发工具 (F12)
// 查找前缀为 [Copilot Pipe] 的日志
console.debug("[Copilot Pipe] 提取的 ChatIDabc123来源__metadata__")
```
**后端日志:**
```python
# Python 日志输出
logger.debug(f"[Copilot Pipe] 会话已恢复:{chat_id}")
```
### 常见问题
#### 1. 会话未恢复
**症状**:每次请求都创建新会话
**原因**
- `chat_id` 提取不正确
- Copilot 端会话过期
- `INFINITE_SESSION=False`(会话不持久)
**解决方案**
```python
# 检查调试日志中的:
"提取的 ChatID<id>(来源:..."
"会话 <id> 未找到(...),正在创建新会话。"
```
#### 2. 系统提示词未应用
**症状**:模型忽略配置的系统提示词
**原因**
- 在 4 个来源中均未找到
- 会话已恢复(系统提示词仅在创建时设置)
**解决方案**
```python
# 检查调试日志中的:
"从 <source> 提取系统提示词长度X"
"配置系统消息模式append"
```
#### 3. 工具不可用
**症状**:模型无法使用自定义工具
**原因**
- `ENABLE_TOOLS=False`
- 工具未在 `_initialize_custom_tools` 中注册
- 错误的 `AVAILABLE_TOOLS` 过滤器
**解决方案**
```python
# 检查调试日志中的:
"已启用 X 个自定义工具:['tool1', 'tool2']"
```
---
## 性能优化
### 模型列表缓存
```python
# 第一次请求:从 API 获取
models = await client.list_models()
self._model_cache = [...] # 缓存结果
# 后续请求:使用缓存
if self._model_cache:
return self._model_cache
```
### 会话持久化
**影响**:消除每次请求的冗余模型初始化
```python
# 没有会话:
# 每次请求:初始化模型 → 加载上下文 → 生成 → 丢弃
# 有会话chat_id
# 第一次请求:初始化模型 → 加载上下文 → 生成 → 保存
# 后续:恢复 → 生成(即时)
```
### 流式 vs 非流式
**流式:**
- 降低感知延迟(首个 token 更快)
- 长响应的更好用户体验
- 通过生成器退出进行资源清理
**非流式:**
- 更简单的错误处理
- 原子响应(无部分输出)
- 用于短响应
---
## 安全考虑
### 令牌保护
```python
# ❌ 永远不要记录令牌
logger.debug(f"令牌:{self.valves.GH_TOKEN}") # 不要这样做
# ✅ 屏蔽敏感数据
logger.debug(f"令牌已配置:{'*' * 10}")
```
### 工作区隔离
```python
# 设置 WORKSPACE_DIR 以限制文件访问
WORKSPACE_DIR = "/safe/sandbox/path"
# Copilot CLI 遵守此目录
client_config["cwd"] = WORKSPACE_DIR
```
### 输入验证
```python
# 验证 chat_id 格式
if chat_id and not re.match(r'^[a-zA-Z0-9_-]+$', chat_id):
logger.warning(f"无效的 chat_id 格式:{chat_id}")
chat_id = None
```
---
## 未来增强
### 计划功能
1. **多会话管理**:支持每个用户的多个并行会话
2. **会话分析**:跟踪 token 使用率、压缩频率
3. **工具结果缓存**:避免冗余工具调用
4. **自定义事件过滤器**:用户可配置的事件处理
5. **工作区模板**:预配置的工作区环境
6. **流式中止**:优雅取消长时间运行的请求
### API 演进
监控 Copilot SDK 更新:
- 新事件类型(例如 `assistant.function_call`
- 增强的工具功能
- 改进的会话序列化
---
## 参考资料
- [GitHub Copilot SDK 文档](https://github.com/github/copilot-sdk)
- [OpenWebUI Pipe 开发](https://docs.openwebui.com/)
- [OpenWebUI Extensions 项目](https://github.com/Fu-Jie/openwebui-extensions)
---
**许可证**MIT
**维护者**Fu-Jie ([@Fu-Jie](https://github.com/Fu-Jie))

View File

@@ -1,124 +0,0 @@
import asyncio
import os
import json
import sys
from copilot import CopilotClient, define_tool
from copilot.types import SessionConfig
from pydantic import BaseModel, Field
# Define a simple tool for testing
class RandomNumberParams(BaseModel):
min: int = Field(description="Minimum value")
max: int = Field(description="Maximum value")
@define_tool(description="Generate a random integer within a range.")
async def generate_random_number(params: RandomNumberParams) -> str:
import random
return f"Result: {random.randint(params.min, params.max)}"
async def main():
print(f"Running tests with Python: {sys.executable}")
# 1. Setup Client
client = CopilotClient({"log_level": "error"})
await client.start()
try:
print("\n=== Test 1: Session Creation & Formatting Injection ===")
# Use gpt-4o or similar capable model
model_id = "gpt-5-mini"
system_message_config = {
"mode": "append",
"content": "You are a test assistant. Always start your response with 'TEST_PREFIX: '.",
}
session_config = SessionConfig(
model=model_id,
system_message=system_message_config,
tools=[generate_random_number],
)
session = await client.create_session(config=session_config)
session_id = session.session_id
print(f"Session Created: {session_id}")
# Test 1.1: Check system prompt effect
resp = await session.send_and_wait(
{"prompt": "Say hello.", "mode": "immediate"}
)
content = resp.data.content
print(f"Response 1: {content}")
if "TEST_PREFIX:" in content:
print("✅ System prompt injection active.")
else:
print("⚠️ System prompt injection NOT detected.")
print("\n=== Test 2: Tool Execution ===")
# Test Tool Usage
prompt_with_tool = (
"Generate a random number between 100 and 200 using the tool."
)
print(f"Sending: {prompt_with_tool}")
# We need to listen to events to verify tool execution,
# but send_and_wait handles it internally and returns the final answer.
# We check if the final answer mentions the result.
resp_tool = await session.send_and_wait(
{"prompt": prompt_with_tool, "mode": "immediate"}
)
tool_content = resp_tool.data.content
print(f"Response 2: {tool_content}")
if "Result:" in tool_content or any(char.isdigit() for char in tool_content):
print("✅ Tool likely executed (numbers found).")
else:
print("⚠️ Tool execution uncertain.")
print("\n=== Test 3: Context Retention (Memory) ===")
# Store a fact
await session.send_and_wait(
{"prompt": "My secret code is 'BLUE-42'. Remember it.", "mode": "immediate"}
)
print("Fact sent.")
# Retrieve it
resp_mem = await session.send_and_wait(
{"prompt": "What is my secret code?", "mode": "immediate"}
)
mem_content = resp_mem.data.content
print(f"Response 3: {mem_content}")
if "BLUE-42" in mem_content:
print("✅ Context retention successful.")
else:
print("⚠️ Context retention failed.")
# Cleanup
await session.destroy()
print("\n=== Test 4: Resume Session (Simulation) ===")
# Note: Actual resuming depends on backend persistence.
# The SDK's client.resume_session(id) tries to find it.
# Since we destroyed it above, we expect failure or new session logic in real app.
# But let's create a new one to persist, close client, and try to resume if process was same?
# Actually persistence usually requires the Copilot Agent/Extension host to keep state or file backed.
# The Python SDK defaults to file-based workspace in standard generic usage?
# Let's just skip complex resume testing for this simple script as it depends on environment (vscode-chat-session vs file).
print("Skipping complex resume test in script.")
except Exception as e:
print(f"Test Failed: {e}")
finally:
await client.stop()
print("\nTests Completed.")
if __name__ == "__main__":
asyncio.run(main())

View File

@@ -1,94 +0,0 @@
import asyncio
import os
import sys
import json
from copilot import CopilotClient
from copilot.types import SessionConfig
# Define the formatting instruction exactly as in the plugin
FORMATTING_INSTRUCTION = (
"\n\n[Formatting Guidelines]\n"
"When providing explanations or descriptions:\n"
"- Use clear paragraph breaks (double line breaks)\n"
"- Break long sentences into multiple shorter ones\n"
"- Use bullet points or numbered lists for multiple items\n"
"- Add headings (##, ###) for major sections\n"
"- Ensure proper spacing between different topics"
)
async def main():
print(f"Python executable: {sys.executable}")
# Check for GH_TOKEN
token = os.environ.get("GH_TOKEN")
if token:
print("GH_TOKEN is set.")
else:
print(
"Warning: GH_TOKEN not found in environment variables. Relying on CLI auth."
)
client_config = {"log_level": "debug"}
client = CopilotClient(client_config)
try:
print("Starting client...")
await client.start()
# Test 1: Check available models
try:
models = await client.list_models()
print(f"Connection successful. Found {len(models)} models.")
model_id = "gpt-5-mini" # User requested model
except Exception as e:
print(f"Failed to list models: {e}")
return
print(f"\nCreating session with model {model_id} and system injection...")
system_message_config = {
"mode": "append",
"content": "You are a helpful assistant." + FORMATTING_INSTRUCTION,
}
session_config = SessionConfig(
model=model_id, system_message=system_message_config
)
session = await client.create_session(config=session_config)
print(f"Session created: {session.session_id}")
# Test 2: Ask the model to summarize its instructions
prompt = "Please summarize the [Formatting Guidelines] you have been given in a list."
print(f"\nSending prompt: '{prompt}'")
response = await session.send_and_wait({"prompt": prompt, "mode": "immediate"})
print("\n--- Model Response ---")
content = response.data.content if response and response.data else "No content"
print(content)
print("----------------------")
required_keywords = ["paragraph", "break", "heading", "spacing", "bullet"]
found_keywords = [kw for kw in required_keywords if kw in content.lower()]
if len(found_keywords) >= 3:
print(
f"\n✅ SUCCESS: Model summarized the guidelines correctly. Found match for: {found_keywords}"
)
else:
print(
f"\n⚠️ UNCERTAIN: Summary might be generic. Found keywords: {found_keywords}"
)
except Exception as e:
print(f"\nError: {e}")
finally:
await client.stop()
print("\nClient stopped.")
if __name__ == "__main__":
asyncio.run(main())

View File

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

View File

@@ -1,31 +0,0 @@
import re
def reproduce_bug():
# 模拟 Issue #49 中提到的受损逻辑
# 核心问题在于正则表达式过于贪婪,或者在多次迭代中错误地将两个加粗块中间的部分当作了“带空格的加粗内容”
text = "I **prefer** tea **to** coffee."
# 模拟一个不严谨的、容易跨块匹配的正则
# 它会匹配 ** 开始,中间任意字符,** 结束
buggy_pattern = re.compile(r"(\*\*)(.*?)(\*\*)")
def buggy_fix(content):
# 模拟插件中的 strip 逻辑:它想去掉加粗符号内部的空格
# 但由于正则匹配了 "**prefer** tea **", 这里的 m.group(2) 变成了 "prefer** tea "
return buggy_pattern.sub(lambda m: f"{m.group(1)}{m.group(2).strip()}{m.group(1)}", content)
# 第一次执行:处理了 "**prefer**" -> "**prefer**"
result_1 = buggy_fix(text)
# 第二次执行(模拟 while 循环或重复运行):
# 此时如果正则引擎从第一个加粗的结束符开始匹配到第二个加粗的起始符
# 指针位置: I **prefer**[匹配开始] tea [匹配结束]**to** coffee.
# 就会把 " tea " 两侧的 ** 当作一对,然后 strip() 掉空格
result_2 = buggy_fix(result_1)
print(f"Original: {text}")
print(f"Step 1: {result_1}")
print(f"Step 2: {result_2} (Bug Reproduced!)")
if __name__ == "__main__":
reproduce_bug()

View File

@@ -1,28 +0,0 @@
import re
def reproduce_bug_v2():
# 模拟更接近旧版实际代码的情况
# 旧版代码中循环多次处理,且正则可能在处理嵌套或连续块时出现偏移
text = "I **prefer** tea **to** coffee."
# 这是一个贪婪且不具备前瞻断言的正则
buggy_pattern = re.compile(r"(\*\*)( +)(.*?)( +)(\*\*)")
# 模拟那种“只要看到 ** 且中间有空格就想修”的逻辑
# 如果文本是 "I **prefer** tea **to**"
# 这里的空格出现在 "prefer**" 和 "**to" 之间
content = "I **prefer** tea **to** coffee."
# 错误的匹配尝试:将第一个块的结尾和第二个块的开头误认为是一对
# I **prefer** tea **to**
# ^^ ^^
# A B
# 正则误以为 A 是开始B 是结束
bug_result = re.sub(r"\*\*( +)(.*?)( +)\*\*", r"**\2**", content)
print(f"Input: {content}")
print(f"Output: {bug_result}")
if __name__ == "__main__":
reproduce_bug_v2()

View File

@@ -1,44 +0,0 @@
import sys
import os
# Add plugin dir to path
current_dir = os.path.dirname(os.path.abspath(__file__))
plugin_dir = os.path.abspath(os.path.join(current_dir, "..", "filters", "markdown_normalizer"))
sys.path.append(plugin_dir)
from markdown_normalizer import ContentNormalizer, NormalizerConfig
def test_latex_protection():
# Test case 1: The reported issue with \times
content_1 = r"Calculation: $C(33, 6) \times C(16, 1)$"
config = NormalizerConfig(enable_escape_fix=True)
normalizer = ContentNormalizer(config)
result_1 = normalizer.normalize(content_1)
print("--- Test 1: \times Protection ---")
print(f"Input: {content_1}")
print(f"Output: {result_1}")
if r"\times" in result_1:
print("✅ PASSED")
else:
print("❌ FAILED")
# Test case 2: Other potential collisions like \nu (newline) or \theta (tab?)
# Using raw strings carefully
content_2 = r"Formula: $\theta = \nu + \tau$"
result_2 = normalizer.normalize(content_2)
print("\n--- Test 2: \theta and \nu Protection ---")
print(f"Input: {content_2}")
print(f"Output: {result_2}")
if r"\theta" in result_2 and r"\nu" in result_2:
print("✅ PASSED")
else:
print("❌ FAILED")
if __name__ == "__main__":
test_latex_protection()

View File

@@ -1,42 +0,0 @@
import re
def verify_fix_v126():
# 1. 准备触发 Bug 的测试文本
test_cases = [
"I **prefer** tea **to** coffee.", # 标准 Issue #49 案例
"The **quick** brown **fox** jumps **over**.", # 多个加粗块
"** text ** and ** more **", # 需要修复的内部空格
"Calculations: 2 * 3 * 4 = 24", # 不应被识别为强调的数学公式
]
# 2. 使用 v1.2.6 中的核心正则表达式 (移除了可能引起解析错误的中文注释)
# 模式: (?<!\*|_)(\*{1,3}|_{1,3})(?P<inner>(?:(?!\1)[^\n])*?)(\1)(?!\*|_)
pattern_str = r"(?<!\*|_)(\*{1,3}|_{1,3})(?P<inner>(?:(?!\1)[^\n])*?)(\1)(?!\*|_)"
FIX_REGEX = re.compile(pattern_str)
def fixed_normalizer(content):
def replacer(match):
symbol = match.group(1)
inner = match.group("inner")
stripped_inner = inner.strip()
# 只有当确实有空格需要修,且内部不是空的才修复
if stripped_inner != inner and stripped_inner:
return f"{symbol}{stripped_inner}{symbol}"
return match.group(0)
# 模拟插件循环处理
for _ in range(2):
content = FIX_REGEX.sub(replacer, content)
return content
print("--- v1.2.6 Fix Verification ---")
for text in test_cases:
result = fixed_normalizer(text)
print(f"Input: {text}")
print(f"Output: {result}")
print("-" * 30)
if __name__ == "__main__":
verify_fix_v126()

View File

@@ -4,7 +4,7 @@ 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
description: 一个专门的过滤器,用于绕过 OpenWebUI 默认的 RAG 机制,针对 GitHub Copilot SDK 模型。它将上传的文件移动到安全位置 ('copilot_files'),以便 Copilot Pipe 可以原生处理它们而不受干扰。
"""

View File

@@ -0,0 +1,192 @@
# 🧭 Agents Stability & Friendliness Guide
This guide focuses on how to improve **reliability** and **user experience** of agents in `github_copilot_sdk.py`.
---
## 1) Goals
- Reduce avoidable failures (timeouts, tool-call dead ends, invalid outputs).
- Keep responses predictable under stress (large context, unstable upstream, partial tool failures).
- Make interaction friendly (clear progress, clarification before risky actions, graceful fallback).
- Preserve backwards compatibility while introducing stronger defaults.
---
## 2) Stability model (4 layers)
## Layer A — Input safety
- Validate essential runtime context early (user/chat/model/tool availability).
- Use strict parsing for JSON-like user/task config (never trust raw free text).
- Add guardrails for unsupported mode combinations (e.g., no tools + tool-required task).
**Implementation hints**
- Add preflight validator before `create_session`.
- Return fast-fail structured errors with recovery actions.
## Layer B — Session safety
- Use profile-driven defaults (`model`, `reasoning_effort`, `infinite_sessions` thresholds).
- Auto-fallback to safe profile when unknown profile is requested.
- Isolate each chat in a deterministic workspace path.
**Implementation hints**
- Add `AGENT_PROFILE` + fallback to `default`.
- Keep `infinite_sessions` enabled by default for long tasks.
## Layer C — Tool-call safety
- Add `on_pre_tool_use` to validate and sanitize args.
- Add denylist/allowlist checks for dangerous operations.
- Add timeout budget per tool class (file/network/shell).
**Implementation hints**
- Keep current `on_post_tool_use` behavior.
- Extend hooks gradually: `on_pre_tool_use` first, then `on_error_occurred`.
## Layer D — Recovery safety
- Retry only idempotent operations with capped attempts.
- Distinguish recoverable vs non-recoverable failures.
- Add deterministic fallback path (summary answer + explicit limitation).
**Implementation hints**
- Retry policy table by event type.
- Emit "what succeeded / what failed / what to do next" blocks.
---
## 3) Friendliness model (UX contract)
## A. Clarification first for ambiguity
Use `on_user_input_request` for:
- Missing constraints (scope, target path, output format)
- High-risk actions (delete/migrate/overwrite)
- Contradictory instructions
**Rule**: ask once with concise choices; avoid repeated back-and-forth.
## B. Progress visibility
Emit status in major phases:
1. Context check
2. Planning/analysis
3. Tool execution
4. Verification
5. Final result
**Rule**: no silent waits > 8 seconds.
## C. Friendly failure style
Every failure should include:
- what failed
- why (short)
- what was already done
- next recommended action
## D. Output readability
Standardize final response blocks:
- `Outcome`
- `Changes`
- `Validation`
- `Limitations`
- `Next Step`
---
## 4) High-value features to add (priority)
## P0 (immediate)
1. `on_user_input_request` handler with default answer strategy
2. `on_pre_tool_use` for argument checks + risk gates
3. Structured progress events (phase-based)
## P1 (short-term)
4. Error taxonomy + retry policy (`network`, `provider`, `tool`, `validation`)
5. Profile-based session factory with safe fallback
6. Auto quality gate for final output sections
## P2 (mid-term)
7. Transport flexibility (`cli_url`, `use_stdio`, `port`) for deployment resilience
8. Azure provider path completion
9. Foreground session lifecycle support for advanced multi-session control
---
## 5) Suggested valves for stability/friendliness
- `AGENT_PROFILE`: `default | builder | analyst | reviewer`
- `ENABLE_USER_INPUT_REQUEST`: `bool`
- `DEFAULT_USER_INPUT_ANSWER`: `str`
- `TOOL_CALL_TIMEOUT_SECONDS`: `int`
- `MAX_RETRY_ATTEMPTS`: `int`
- `ENABLE_SAFE_TOOL_GUARD`: `bool`
- `ENABLE_PHASE_STATUS_EVENTS`: `bool`
- `ENABLE_FRIENDLY_FAILURE_TEMPLATE`: `bool`
---
## 6) Failure playbooks (practical)
## Playbook A — Provider timeout
- Retry once if request is idempotent.
- Downgrade reasoning effort if timeout persists.
- Return concise fallback and preserve partial result.
## Playbook B — Tool argument mismatch
- Block execution in `on_pre_tool_use`.
- Ask user one clarification question if recoverable.
- Otherwise skip tool and explain impact.
## Playbook C — Large output overflow
- Save large output to workspace file.
- Return file path + short summary.
- Avoid flooding chat with huge payload.
## Playbook D — Conflicting user instructions
- Surface conflict explicitly.
- Offer 2-3 fixed choices.
- Continue only after user selection.
---
## 7) Metrics to track
- Session success rate
- Tool-call success rate
- Average recovery rate after first failure
- Clarification rate vs hallucination rate
- Mean time to first useful output
- User follow-up dissatisfaction signals (e.g., “not what I asked”)
---
## 8) Minimal rollout plan
1. Add `on_user_input_request` + `on_pre_tool_use` (feature-gated).
2. Add phase status events and friendly failure template.
3. Add retry policy + error taxonomy.
4. Add profile fallback and deployment transport options.
5. Observe metrics for 1-2 weeks, then tighten defaults.
---
## 9) Quick acceptance checklist
- Agent asks clarification only when necessary.
- No long silent period without status updates.
- Failures always include next actionable step.
- Unknown profile/provider config does not crash session.
- Large outputs are safely redirected to file.
- Final response follows a stable structure.

View File

@@ -0,0 +1,192 @@
# 🧭 Agents 稳定性与友好性指南
本文聚焦如何提升 `github_copilot_sdk.py` 中 Agent 的**稳定性**与**交互友好性**。
---
## 1目标
- 降低可避免失败(超时、工具死路、输出不可解析)。
- 在高压场景保持可预期(大上下文、上游不稳定、部分工具失败)。
- 提升交互体验(进度可见、风险操作先澄清、优雅降级)。
- 在不破坏兼容性的前提下逐步增强默认行为。
---
## 2稳定性模型4 层)
## A 层:输入安全
- 会话创建前验证关键上下文user/chat/model/tool 可用性)。
- 对 JSON/配置采用严格解析,不信任自由文本。
- 对不支持的模式组合做前置拦截(例如:任务需要工具但工具被禁用)。
**落地建议**
- `create_session` 前增加 preflight validator。
- 快速失败并返回结构化恢复建议。
## B 层:会话安全
- 使用 profile 驱动默认值(`model``reasoning_effort``infinite_sessions`)。
- 请求未知 profile 时自动回退到安全默认 profile。
- 每个 chat 使用确定性 workspace 路径隔离。
**落地建议**
- 增加 `AGENT_PROFILE`,未知值回退 `default`
- 长任务默认开启 `infinite_sessions`
## C 层:工具调用安全
- 增加 `on_pre_tool_use` 做参数校验与净化。
- 增加高风险操作 allow/deny 规则。
- 按工具类别配置超时预算(文件/网络/命令)。
**落地建议**
- 保留现有 `on_post_tool_use`
- 先补 `on_pre_tool_use`,再补 `on_error_occurred`
## D 层:恢复安全
- 仅对幂等操作重试,且有次数上限。
- 区分可恢复/不可恢复错误。
- 提供确定性降级输出(摘要 + 限制说明)。
**落地建议**
- 按错误类型配置重试策略。
- 统一输出“成功了什么 / 失败了什么 / 下一步”。
---
## 3友好性模型UX 合约)
## A. 歧义先澄清
通过 `on_user_input_request` 处理:
- 约束缺失(范围、目标路径、输出格式)
- 高风险动作(删除/迁移/覆盖)
- 用户指令互相冲突
**规则**:一次提问给出有限选项,避免反复追问。
## B. 进度可见
按阶段发状态:
1. 上下文检查
2. 规划/分析
3. 工具执行
4. 验证
5. 结果输出
**规则**:超过 8 秒不能无状态输出。
## C. 失败友好
每次失败都要包含:
- 失败点
- 简短原因
- 已完成部分
- 下一步可执行建议
## D. 输出可读
统一最终输出结构:
- `Outcome`
- `Changes`
- `Validation`
- `Limitations`
- `Next Step`
---
## 4高价值增强项优先级
## P0立即
1. `on_user_input_request` + 默认答复策略
2. `on_pre_tool_use` 参数检查 + 风险闸门
3. 阶段化状态事件
## P1短期
4. 错误分类 + 重试策略(`network/provider/tool/validation`
5. profile 化 session 工厂 + 安全回退
6. 最终输出质量门(结构校验)
## P2中期
7. 传输配置能力(`cli_url/use_stdio/port`
8. Azure provider 支持完善
9. foreground session 生命周期能力(高级多会话)
---
## 5建议新增 valves
- `AGENT_PROFILE`: `default | builder | analyst | reviewer`
- `ENABLE_USER_INPUT_REQUEST`: `bool`
- `DEFAULT_USER_INPUT_ANSWER`: `str`
- `TOOL_CALL_TIMEOUT_SECONDS`: `int`
- `MAX_RETRY_ATTEMPTS`: `int`
- `ENABLE_SAFE_TOOL_GUARD`: `bool`
- `ENABLE_PHASE_STATUS_EVENTS`: `bool`
- `ENABLE_FRIENDLY_FAILURE_TEMPLATE`: `bool`
---
## 6故障应对手册实用
## 场景 AProvider 超时
- 若请求幂等,重试一次。
- 仍超时则降低 reasoning 强度。
- 返回简洁降级结果并保留已有中间成果。
## 场景 B工具参数不匹配
-`on_pre_tool_use` 阻断。
- 可恢复则提一个澄清问题。
- 不可恢复则跳过工具并说明影响。
## 场景 C输出过大
- 大输出落盘到 workspace 文件。
- 返回文件路径 + 简要摘要。
- 避免把超大内容直接刷屏。
## 场景 D用户指令冲突
- 明确指出冲突点。
- 给 2-3 个固定选项。
- 用户选定后再继续。
---
## 7建议监控指标
- 会话成功率
- 工具调用成功率
- 首次失败后的恢复率
- 澄清率 vs 幻觉率
- 首次可用输出耗时
- 用户不满意信号(如“不是我要的”)
---
## 8最小落地路径
1. 先加 `on_user_input_request` + `on_pre_tool_use`(功能开关控制)。
2. 增加阶段状态事件和失败友好模板。
3. 增加错误分类与重试策略。
4. 增加 profile 安全回退与传输配置能力。
5. 观察 1-2 周指标,再逐步收紧默认策略。
---
## 9验收速查
- 仅在必要时澄清,不重复追问。
- 无长时间无状态“沉默”。
- 失败输出包含下一步动作。
- profile/provider 配置异常不导致会话崩溃。
- 超大输出可安全转文件。
- 最终响应结构稳定一致。

View File

@@ -0,0 +1,294 @@
# 🤖 Custom Agents Reference (Copilot SDK Python)
This document explains how to create **custom agent profiles** using the SDK at:
- `/Users/fujie/app/python/oui/copilot-sdk/python`
and apply them in this pipe:
- `plugins/pipes/github-copilot-sdk/github_copilot_sdk.py`
---
## 1) What is a “Custom Agent” here?
In Copilot SDK Python, a custom agent is not a separate runtime class from the SDK itself.
It is typically a **session configuration bundle**:
- model + reasoning level
- system message/persona
- tools exposure
- hooks lifecycle behavior
- user input strategy
- infinite session compaction strategy
- provider (optional BYOK)
So the practical implementation is:
1. Define an `AgentProfile` data structure.
2. Convert profile -> `session_config`.
3. Call `client.create_session(session_config)`.
---
## 2) SDK capabilities you can use
From `copilot-sdk/python/README.md`, the key knobs are:
- `model`
- `reasoning_effort`
- `tools`
- `system_message`
- `streaming`
- `provider`
- `infinite_sessions`
- `on_user_input_request`
- `hooks`
These are enough to create different agent personas without forking core logic.
---
## 3) Recommended architecture in pipe
Use a **profile registry** + a single factory method.
```python
from dataclasses import dataclass
from typing import Any, Callable, Optional
@dataclass
class AgentProfile:
name: str
model: str
reasoning_effort: str = "medium"
system_message: Optional[str] = None
enable_tools: bool = True
enable_openwebui_tools: bool = True
enable_hooks: bool = False
enable_user_input: bool = False
infinite_sessions_enabled: bool = True
compaction_threshold: float = 0.8
buffer_exhaustion_threshold: float = 0.95
```
Then map profile -> session config:
```python
def build_session_config(profile: AgentProfile, tools: list, hooks: dict, user_input_handler: Optional[Callable[..., Any]]):
config = {
"model": profile.model,
"reasoning_effort": profile.reasoning_effort,
"streaming": True,
"infinite_sessions": {
"enabled": profile.infinite_sessions_enabled,
"background_compaction_threshold": profile.compaction_threshold,
"buffer_exhaustion_threshold": profile.buffer_exhaustion_threshold,
},
}
if profile.system_message:
config["system_message"] = {"content": profile.system_message}
if profile.enable_tools:
config["tools"] = tools
if profile.enable_hooks and hooks:
config["hooks"] = hooks
if profile.enable_user_input and user_input_handler:
config["on_user_input_request"] = user_input_handler
return config
```
---
## 4) Example profile presets
```python
AGENT_PROFILES = {
"builder": AgentProfile(
name="builder",
model="claude-sonnet-4.6",
reasoning_effort="high",
system_message="You are a precise coding agent. Prefer minimal, verifiable changes.",
enable_tools=True,
enable_hooks=True,
),
"analyst": AgentProfile(
name="analyst",
model="gpt-5-mini",
reasoning_effort="medium",
system_message="You analyze and summarize with clear evidence mapping.",
enable_tools=False,
enable_hooks=False,
),
"reviewer": AgentProfile(
name="reviewer",
model="claude-sonnet-4.6",
reasoning_effort="high",
system_message="Review diffs, identify risks, and propose minimal fixes.",
enable_tools=True,
enable_hooks=True,
),
}
```
---
## 5) Integrating with this pipe
In `github_copilot_sdk.py`:
1. Add a Valve like `AGENT_PROFILE` (default: `builder`).
2. Resolve profile from registry at runtime.
3. Build `session_config` from profile.
4. Merge existing valve toggles (`ENABLE_TOOLS`, `ENABLE_OPENWEBUI_TOOLS`) as final override.
Priority recommendation:
- explicit runtime override > valve toggle > profile default
This keeps backward compatibility while enabling profile-based behavior.
---
## 6) Hook strategy (safe defaults)
Use hooks only when needed:
- `on_pre_tool_use`: allow/deny tools, sanitize args
- `on_post_tool_use`: add short execution context
- `on_user_prompt_submitted`: normalize unsafe prompt patterns
- `on_error_occurred`: retry/skip/abort policy
Start with no-op hooks, then incrementally enforce policy.
---
## 7) Validation checklist
- Profile can be selected by valve and takes effect.
- Session created with expected model/reasoning.
- Tool availability matches profile + valve overrides.
- Hook handlers run only when enabled.
- Infinite-session compaction settings are applied.
- Fallback to default profile if unknown profile name is provided.
---
## 8) Anti-patterns to avoid
- Hardcoding profile behavior in multiple places.
- Mixing tool registration logic with prompt-format logic.
- Enabling expensive hooks for all profiles by default.
- Coupling profile name to exact model id with no fallback.
---
## 9) Minimal rollout plan
1. Add profile dataclass + registry.
2. Add one valve: `AGENT_PROFILE`.
3. Build session config factory.
4. Keep existing behavior as default profile.
5. Add 2 more profiles (`analyst`, `reviewer`) and test.
---
## 10) SDK gap analysis for current pipe (high-value missing features)
Current pipe already implements many advanced capabilities:
- `SessionConfig` with `tools`, `system_message`, `infinite_sessions`, `provider`, `mcp_servers`
- Session resume/create path
- `list_models()` cache path
- Attachments in `session.send(...)`
- Hook integration (currently `on_post_tool_use`)
Still missing (or partially implemented) high-value SDK features:
### A. `on_user_input_request` handler (ask-user loop)
**Why valuable**
- Enables safe clarification for ambiguous tasks instead of hallucinated assumptions.
**Current state**
- Not wired into `create_session(...)`.
**Implementation idea**
- Add valves:
- `ENABLE_USER_INPUT_REQUEST: bool`
- `DEFAULT_USER_INPUT_ANSWER: str`
- Add a handler function and pass:
- `session_params["on_user_input_request"] = handler`
### B. Full lifecycle hooks (beyond `on_post_tool_use`)
**Why valuable**
- Better policy control and observability.
**Current state**
- Only `on_post_tool_use` implemented.
**Implementation idea**
- Add optional handlers for:
- `on_pre_tool_use`
- `on_user_prompt_submitted`
- `on_session_start`
- `on_session_end`
- `on_error_occurred`
### C. Provider type coverage gap (`azure`)
**Why valuable**
- Azure OpenAI users cannot configure provider type natively.
**Current state**
- Valve type only allows `openai | anthropic`.
**Implementation idea**
- Extend valve enum to include `azure`.
- Add `BYOK_AZURE_API_VERSION` valve.
- Build `provider` payload with `azure` block when selected.
### D. Client transport options exposure (`cli_url`, `use_stdio`, `port`)
**Why valuable**
- Enables remote/shared Copilot server and tuning transport mode.
**Current state**
- `_build_client_config` sets `cli_path/cwd/config_dir/log_level/env`, but not transport options.
**Implementation idea**
- Add valves:
- `COPILOT_CLI_URL`
- `COPILOT_USE_STDIO`
- `COPILOT_PORT`
- Conditionally inject into `client_config`.
### E. Foreground session lifecycle APIs
**Why valuable**
- Better multi-session UX and control in TUI/server mode.
**Current state**
- No explicit usage of:
- `get_foreground_session_id()`
- `set_foreground_session_id()`
- `client.on("session.foreground", ...)`
**Implementation idea**
- Optional debug/admin feature only.
- Add event bridge for lifecycle notifications.
---
## 11) Recommended implementation priority
1. `on_user_input_request` (highest value / low risk)
2. Full lifecycle hooks (high value / medium risk)
3. Azure provider support (high value for enterprise users)
4. Client transport valves (`cli_url/use_stdio/port`)
5. Foreground session APIs (optional advanced ops)

View File

@@ -0,0 +1,292 @@
# 🤖 自定义 Agents 参考文档Copilot SDK Python
本文说明如何基于以下 SDK 创建**可复用的自定义 Agent 配置**
- `/Users/fujie/app/python/oui/copilot-sdk/python`
并接入当前 Pipe
- `plugins/pipes/github-copilot-sdk/github_copilot_sdk.py`
---
## 1这里的“自定义 Agent”是什么
在 Copilot SDK Python 中,自定义 Agent 通常不是 SDK 里的独立类,而是一个**会话配置组合**
- 模型与推理强度
- system message / 人设
- tools 暴露范围
- hooks 生命周期行为
- 用户输入策略
- infinite session 压缩策略
- provider可选
实际落地方式:
1. 定义 `AgentProfile` 数据结构。
2. 将 profile 转成 `session_config`
3. 调用 `client.create_session(session_config)`
---
## 2SDK 可用于定制 Agent 的能力
根据 `copilot-sdk/python/README.md`,关键可配置项包括:
- `model`
- `reasoning_effort`
- `tools`
- `system_message`
- `streaming`
- `provider`
- `infinite_sessions`
- `on_user_input_request`
- `hooks`
这些能力足够做出多个 agent 人设,而无需复制整套管线代码。
---
## 3在 Pipe 中推荐的架构
建议采用:**Profile 注册表 + 单一工厂函数**。
```python
from dataclasses import dataclass
from typing import Any, Callable, Optional
@dataclass
class AgentProfile:
name: str
model: str
reasoning_effort: str = "medium"
system_message: Optional[str] = None
enable_tools: bool = True
enable_openwebui_tools: bool = True
enable_hooks: bool = False
enable_user_input: bool = False
infinite_sessions_enabled: bool = True
compaction_threshold: float = 0.8
buffer_exhaustion_threshold: float = 0.95
```
profile -> session_config 的工厂函数:
```python
def build_session_config(profile: AgentProfile, tools: list, hooks: dict, user_input_handler: Optional[Callable[..., Any]]):
config = {
"model": profile.model,
"reasoning_effort": profile.reasoning_effort,
"streaming": True,
"infinite_sessions": {
"enabled": profile.infinite_sessions_enabled,
"background_compaction_threshold": profile.compaction_threshold,
"buffer_exhaustion_threshold": profile.buffer_exhaustion_threshold,
},
}
if profile.system_message:
config["system_message"] = {"content": profile.system_message}
if profile.enable_tools:
config["tools"] = tools
if profile.enable_hooks and hooks:
config["hooks"] = hooks
if profile.enable_user_input and user_input_handler:
config["on_user_input_request"] = user_input_handler
return config
```
---
## 4示例 Profile 预设
```python
AGENT_PROFILES = {
"builder": AgentProfile(
name="builder",
model="claude-sonnet-4.6",
reasoning_effort="high",
system_message="You are a precise coding agent. Prefer minimal, verifiable changes.",
enable_tools=True,
enable_hooks=True,
),
"analyst": AgentProfile(
name="analyst",
model="gpt-5-mini",
reasoning_effort="medium",
system_message="You analyze and summarize with clear evidence mapping.",
enable_tools=False,
enable_hooks=False,
),
"reviewer": AgentProfile(
name="reviewer",
model="claude-sonnet-4.6",
reasoning_effort="high",
system_message="Review diffs, identify risks, and propose minimal fixes.",
enable_tools=True,
enable_hooks=True,
),
}
```
---
## 5如何接入当前 Pipe
`github_copilot_sdk.py` 中:
1. 新增 Valve`AGENT_PROFILE`(默认 `builder`)。
2. 运行时从注册表解析 profile。
3. 通过工厂函数生成 `session_config`
4. 把已有开关(如 `ENABLE_TOOLS``ENABLE_OPENWEBUI_TOOLS`)作为最终覆盖层。
推荐优先级:
- 显式运行时参数 > valve 开关 > profile 默认值
这样能保持向后兼容,同时支持按 profile 切换 agent 行为。
---
## 6Hooks 策略(安全默认)
仅在必要时开启 hooks
- `on_pre_tool_use`:工具调用前 allow/deny、参数净化
- `on_post_tool_use`:补充简要上下文
- `on_user_prompt_submitted`:提示词规范化
- `on_error_occurred`:错误重试/跳过/中止策略
建议先用 no-op再逐步加策略。
---
## 7验证清单
- 可通过 valve 选择 profile且生效。
- session 使用了预期 model / reasoning。
- 工具可用性符合 profile + valve 覆盖后的结果。
- hooks 仅在启用时触发。
- infinite session 的阈值配置已生效。
- 传入未知 profile 时能安全回退到默认 profile。
---
## 8常见反模式
- 把 profile 逻辑硬编码在多个位置。
- 将工具注册逻辑与提示词格式化耦合。
- 默认给所有 profile 开启高开销 hooks。
- profile 名与模型 ID 强绑定且没有回退方案。
---
## 9最小落地步骤
1. 增加 profile dataclass + registry。
2. 增加一个 valve`AGENT_PROFILE`
3. 增加 session_config 工厂函数。
4. 将现有行为作为 default profile。
5. 再加 `analyst``reviewer` 两个 profile 并验证。
---
## 10当前 Pipe 的 SDK 能力差距(高价值项)
当前 pipe 已实现不少高级能力:
- `SessionConfig` 里的 `tools``system_message``infinite_sessions``provider``mcp_servers`
- session 的 resume/create 路径
- `list_models()` 模型缓存路径
- `session.send(...)` 附件传递
- hooks 接入(目前仅 `on_post_tool_use`
但仍有高价值能力未实现或仅部分实现:
### A. `on_user_input_request`ask-user 交互回路)
**价值**
- 任务不明确时可主动追问,降低错误假设和幻觉。
**现状**
- 尚未接入 `create_session(...)`
**实现建议**
- 增加 valves
- `ENABLE_USER_INPUT_REQUEST: bool`
- `DEFAULT_USER_INPUT_ANSWER: str`
-`session_params` 中注入:
- `session_params["on_user_input_request"] = handler`
### B. 完整生命周期 hooks不仅 `on_post_tool_use`
**价值**
- 增强策略控制与可观测性。
**现状**
- 目前只实现了 `on_post_tool_use`
**实现建议**
- 增加可选 handler
- `on_pre_tool_use`
- `on_user_prompt_submitted`
- `on_session_start`
- `on_session_end`
- `on_error_occurred`
### C. Provider 类型覆盖缺口(`azure`
**价值**
- 企业 Azure OpenAI 场景可直接接入。
**现状**
- valve 仅支持 `openai | anthropic`
**实现建议**
- 扩展枚举支持 `azure`
- 增加 `BYOK_AZURE_API_VERSION`
- 选择 azure 时构造 provider 的 `azure` 配置块。
### D. Client 传输配置未暴露(`cli_url` / `use_stdio` / `port`
**价值**
- 支持远程/共享 Copilot 服务,便于部署与调优。
**现状**
- `_build_client_config` 仅设置 `cli_path/cwd/config_dir/log_level/env`
**实现建议**
- 增加 valves
- `COPILOT_CLI_URL`
- `COPILOT_USE_STDIO`
- `COPILOT_PORT`
-`client_config` 中按需注入。
### E. 前台会话生命周期 API 未使用
**价值**
- 多会话/运维场景下可增强可控性与可视化。
**现状**
- 尚未显式使用:
- `get_foreground_session_id()`
- `set_foreground_session_id()`
- `client.on("session.foreground", ...)`
**实现建议**
- 作为 debug/admin 高级功能逐步接入。
---
## 11建议实现优先级
1. `on_user_input_request`(收益高、风险低)
2. 完整 lifecycle hooks收益高、风险中
3. Azure provider 支持(企业价值高)
4. client 传输配置 valves`cli_url/use_stdio/port`
5. 前台会话生命周期 API高级可选

View File

@@ -1,8 +1,14 @@
# GitHub Copilot SDK Pipe for OpenWebUI
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.9.0 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
**Author:** [Fu-Jie](https://github.com/Fu-Jie) | **Version:** 0.9.1 | **Project:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **License:** MIT
This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/open-webui) that integrates the official [GitHub Copilot SDK](https://github.com/github/copilot-sdk). It enables you to use **GitHub Copilot models** (e.g., `gpt-5.2-codex`, `claude-sonnet-4.5`,`gemini-3-pro`, `gpt-5-mini`) **AND** your own models via **BYOK** (OpenAI, Anthropic) directly within OpenWebUI, providing a unified agentic experience with **strict User & Chat-level Workspace Isolation**.
This is a powerful **GitHub Copilot SDK** Pipe for **OpenWebUI** that provides a unified **Agentic experience**. It goes beyond simple model access by enabling autonomous **Intent Recognition**, **Web Search**, and **Context Compaction**. It seamlessly reuses your existing **Tools, MCP servers, OpenAPI servers, and Skills** from OpenWebUI to create a truly integrated ecosystem.
- **🧠 Autonomous Intent Recognition**: The Agent independently analyzes user goals to determine the most effective path forward.
- **🌐 Smart Web Search**: Built-in capability to trigger web searches autonomously based on task requirements.
- **♾️ Infinite Session (Context Compaction)**: Automatically manages long-running conversations by compacting context (summarization + TODO persistence) to maintain project focus.
- **🧩 Ecosystem Injection**: Directly reads and leverages your configured **OpenWebUI Tools, MCPs, OpenAPI Servers, and Skills**.
- **🎨 Interactive Delivery**: Native support for **HTML Artifacts** and **RichUI** components for real-time visualization and reporting.
> [!IMPORTANT]
> **Essential Companion**
@@ -14,21 +20,19 @@ This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/
---
## ✨ v0.9.0: The Skills Revolution & Stability Update
## ✨ v0.9.1: Autonomous Web Search & Reliability Fix
- **🧩 Copilot SDK Skills Support**: Native support for Copilot SDK skill directories (`SKILL.md` + resources). Skills can now be loaded as first-class runtime context.
- **🔄 OpenWebUI Skills Bridge**: Full bidirectional sync between OpenWebUI **Workspace > Skills** and SDK skill directories.
- **🛠️ Deterministic `manage_skills` Tool**: Expert tool for stable install/create/list/edit/delete skill operations.
- **🌊 Reinforced Status Bar**: Multi-layered locking mechanism (`session_finalized` guard) and atomic async delivery to prevent "stuck" indicators.
- **⚡ Asynchronous Integrity**: Refactored status emission to route all updates through a centralized helper, ensuring atomic delivery and preventing race conditions in parallel execution streams.
- **💓 Pulse-Lock Refresh**: Implemented a hardware-inspired "pulse" logic that forces a final UI state refresh at the end of each session, ensuring the status bar settling on "Task completed."
- **🗂️ Persistent Config Directory**: Added `COPILOTSDK_CONFIG_DIR` for stable session-state persistence across container restarts.
- **🌐 Autonomous Web Search**: `web_search` is now always enabled for the Agent (bypassing the UI toggle), leveraging the Copilot SDK's native ability to decide when to search.
- **🛠️ Terminology Alignment**: Standardized all references to **"Agent"** and **"Context Compaction"** (for Infinite Session) across all languages to better reflect the technical capabilities.
- **🌐 Language Consistency**: System prompts mandate that Agent output language remains strictly consistent with user input.
- **🐛 Fixed MCP Tool Filtering**: Resolved a critical issue where configuring `function_name_filter_list` (or selecting specific tools in UI) would cause all tools from that MCP server to be incorrectly hidden due to ID prefix mismatches (`server:mcp:`).
- **🔍 Improved Filter Stability**: Ensured tool-level whitelists apply reliably without breaking the entire server connection.
---
## ✨ Key Capabilities
- **🔑 Unified Intelligence (Official + BYOK)**: Seamlessly switch between official GitHub Copilot models (o1, GPT-4o, Claude 3.5 Sonnet, Gemini 2.0 Flash) and your own models (OpenAI, Anthropic) via **Bring Your Own Key** mode.
- **🔑 Unified Intelligence (Official + BYOK)**: Seamlessly switch between official GitHub Copilot models and your own models (OpenAI, Anthropic, DeepSeek, xAI) via **Bring Your Own Key** mode.
- **🛡️ Physical Workspace Isolation**: Every session runs in its own isolated directory sandbox. This ensures absolute data privacy and prevents cross-chat file contamination while allowing the Agent full filesystem access.
- **🔌 Universal Tool Protocol**:
- **Native MCP**: Direct, high-performance connection to Model Context Protocol servers.
@@ -40,7 +44,13 @@ This is an advanced Pipe function for [OpenWebUI](https://github.com/open-webui/
- **Live HTML/JS**: Instantly render and interact with apps, dashboards, or reports generated by the Agent.
- **Persistent Publishing**: Agents can "publish" generated files (Excel, CSV, docs) to OpenWebUI's file storage, providing permanent download links.
- **🌊 UX-First Streaming**: Full support for "Thinking" processes (Chain of Thought), status indicators, and real-time progress bars for long-running tasks.
- **🧠 Deep Database Integration**: Real-time persistence of TOD·O lists and session metadata ensures your workflow state is always visible in the UI.
- **🧠 Deep Database Integration**: Real-time persistence of TODO lists and session metadata ensures your workflow state is always visible in the UI.
> [!TIP]
> **💡 Visualization Pro-Tip**
> To get the most out of **HTML Artifacts** and **RichUI**, we highly recommend asking the Agent to install the skill via its GitHub URL:
> "Install this skill: <https://github.com/nicobailon/visual-explainer>".
> This skill is specifically optimized for generating high-quality visual components and integrates perfectly with this Pipe.
---

View File

@@ -1,8 +1,16 @@
# GitHub Copilot SDK 官方管道
# GitHub Copilot Official SDK Pipe
**作者:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **版本:** 0.9.0 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可证:** MIT
**作者:** [Fu-Jie](https://github.com/Fu-Jie/openwebui-extensions) | **版本:** 0.9.1 | **项目:** [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) | **许可证:** MIT
这是一个用于 [OpenWebUI](https://github.com/open-webui/open-webui) 的高级 Pipe 函数,深度集成了 **GitHub Copilot SDK**。它不仅支持 **GitHub Copilot 官方模型**(如 `gpt-5.2-codex`, `claude-sonnet-4.5`, `gemini-3-pro`, `gpt-5-mini`),还支持 **BYOK (自带 Key)** 模式对接自定义服务商OpenAI, Anthropic并具备**严格的用户与会话级工作区隔离**能力,提供统一且安全的 Agent 交互体验。
这是一个**GitHub Copilot SDK** 深度集成到 **OpenWebUI** 中的强大 Agent SDK 管道。它不仅实现了 SDK 的核心功能,还支持 **智能意图识别**、**自主网页搜索** 与 **自动上下文压缩**,并能够无缝读取 OpenWebUI 已有的配置进行智能注入,让 Agent 能够具备以下能力:
- **🧠 智能意图识别**Agent 能自主分析用户任务的深层意图,决定最有效的处理路径。
- **🌐 自主网页搜索**:具备独立的网页搜索触发判断力,无需用户手动干预。
- **♾️ 自动压缩上下文**:支持 Infinite Session自动对长对话进行上下文压缩与摘要确保长期任务跟进。
- **🛠️ 全功能 Skill 体系**:完美支持本地自定义 Skill 目录,通过脚本与资源的结合实现真正的功能增强。
- **🧩 深度生态复用**:直接复用您在 OpenWebUI 中配置的各种 **工具 (Tools)**、**MCP**、**OpenAPI Server** 和 **技能 (Skills)**
为您带来更强、更完整的交互体验。
> [!IMPORTANT]
> **核心伴侣组件**
@@ -13,31 +21,37 @@
---
## ✨ 0.9.0 核心更新:技能革命与稳定性加固
## ✨ 0.9.1 最新更新:自主网页搜索与可靠性修复
- **🧩 Copilot SDK Skills 原生支持**: 技能可作为一等上下文能力被加载和使用
- **🔄 OpenWebUI Skills 桥接**: 实现 OpenWebUI **工作区 > Skills** 与 SDK 技能目录的深度双向同步
- **🛠️ 确定性 `manage_skills` 工具**: 通过稳定工具契约完成技能的生命周期管理
- **🌊 状态栏逻辑加固**: 引入 `session_finalized` 多层锁定机制,彻底解决任务完成后状态栏回弹或卡死的问题。
- **🗂️ 环境目录持久化**: 增强 `COPILOTSDK_CONFIG_DIR` 逻辑,确保会话状态跨容器重启稳定存在
- **🌐 强化自主网页搜索**`web_search` 工具现已强制对 Agent 开启(绕过 UI 网页搜索开关),充分利用 Copilot 自身具备的搜索判断能力
- **🛠️ 术语一致性优化**:全语种同步将“助手”更改为 **"Agent"**,并将“优化会话”统一为 **"压缩上下文"**,更准确地描述 Infinite Session 的技术本质
- **🌐 语言一致性**:内置指令确保 Agent 输出语言与用户输入严格对齐,提供无缝的国际化交互体验
- **🐛 修复 MCP 工具过滤逻辑**:解决了在管理员后端配置 `function_name_filter_list`(或在聊天界面勾选特定工具)时,因 ID 前缀(`server:mcp:`)识别逻辑错误导致工具意外失效的问题。
- **🔍 提升过滤稳定性**:修复了工具 ID 归一化逻辑,确保点选的工具白名单在 SDK 会话中精确生效
---
## ✨ 核心能力 (Key Capabilities)
- **🔑 统一智能体验 (官方 + BYOK)**: 自由切换官方模型o1, GPT-4o, Claude 3.5 Sonnet, Gemini 2.0 Flash与自定义服务商OpenAI, Anthropic支持 **BYOK (自带 Key)** 模式。
- **🔑 统一智能体验 (官方 + BYOK)**: 自由切换官方模型与自定义服务商OpenAI, Anthropic, DeepSeek, xAI),支持 **BYOK (自带 Key)** 模式。
- **🛡️ 物理级工作区隔离**: 每个会话在独立的沙箱目录中运行。确保绝对的数据隐私,防止不同聊天间的文件污染,同时给予 Agent 完整的文件系统操作权限。
- **🔌 通用工具协议**:
- **原生 MCP**: 高性能直连 Model Context Protocol 服务器。
- **OpenAPI 桥接**: 将任何外部 REST API 一键转换为 Agent 可调用的工具。
- **OpenWebUI 原生桥接**: 零配置接入现有的 OpenWebUI 工具及内置功能(网页搜索、记忆等)。
- **🧩 OpenWebUI Skills 桥接**: 将简单的 OpenWebUI Markdown 指令转化为包含脚本、模板数据的强大 SDK 技能文件夹。
- **🧩 OpenWebUI Skills 桥接**: 将简单的 OpenWebUI Markdown 指令转化为包含脚本、模板 and 数据的强大 SDK 技能文件夹。
- **♾️ 无限会话管理**: 先进的上下文窗口管理,支持自动“压缩”(摘要提取 + TODO 列表持久化)。支持长达数周的项目跟踪而不会丢失核心上下文。
- **📊 交互式产物与发布**:
- **实时 HTML/JS**: 瞬间渲染并交互 Agent 生成的应用程序、可视化看板或报告。
- **持久化发布**: Agent 可将生成的产物Excel, CSV, 文档)发布至 OpenWebUI 文件存储,并在聊天中提供永久下载链接。
- **🌊 极致交互体验**: 完整支持深度思考过程 (Thinking Process) 流式渲染、状态指示器以及长任务实时进度条。
- **🧠 深度数据库集成**: TOD·O 列表与会话元数据的实时持久化,确保任务执行状态在 UI 上清晰可见。
- **🧠 深度数据库集成**: TODO 列表与会话元数据的实时持久化,确保任务执行状态在 UI 上清晰可见。
> [!TIP]
> **💡 增强渲染建议**
> 为了获得最精美的 **HTML Artifacts** 与 **RichUI** 效果,建议在对话中通过提供的 GitHub 链接直接命令 Agent 安装:
> “请安装此技能:<https://github.com/nicobailon/visual-explainer”。>
> 该技能专为生成高质量可视化组件而设计,能够与本 Pipe 完美协作。
---
@@ -46,162 +60,51 @@
`GitHub Copilot SDK Files Filter` 是本 Pipe 的配套插件,用于阻止 OpenWebUI 默认 RAG 在 Pipe 接手前抢先处理上传文件。
- **作用**: 将上传文件移动到 `copilot_files`,让 Pipe 能直接读取原始二进制。
- **必要性**: 若未安装,文件可能被提前解析/向量化Agent 难以拿到原始文件。
- **必要性**: 若未安装,文件可能被提前解析/向量化Agent 拿到原始文件。
- **v0.1.3 重点**:
- 修复 BYOK 模型 ID 识别(支持 `github_copilot_official_sdk_pipe.xxx` 前缀匹配)。
- 新增双通道调试日志(`show_debug_log`):后端 logger + 浏览器控制台。
---
## ⚙️ 核心配置参数 (Valves)
## 🚀 快速开始 (Quick Start)
### 1. 管理员配置 (基础设置)
1. **安装本插件**: 在 OpenWebUI 管道管理界面添加并启用。
2. **安装 [Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6)** (必须): 以获得文件处理能力。
3. **配置凭据**:
- **官方模式**: 默认即可。确保环境中安装了 `github-copilot-sdk`
- **BYOK 模式**: 填入 OpenAI/Anthropic/DeepSeek 的 Base URL 与 Key。
4. **选择模型**: 在聊天界面选择 `GitHub Copilot Official SDK Pipe` 系列模型。
5. **开始对话**: 直接上传文件或发送复杂指令。
管理员可在函数设置中定义全局默认行为。
---
| 参数 | 默认值 | 说明 |
## ⚙️ 配置参数 (Configuration Valves)
| 参数 | 默认值 | 描述 |
| :--- | :--- | :--- |
| `GH_TOKEN` | `""` | 全局 GitHub Token (需具备 'Copilot Requests' 权限)。 |
| `COPILOTSDK_CONFIG_DIR` | `""` | SDK 配置与会话状态持久化目录 (例如: `/app/backend/data/.copilot`)。 |
| `ENABLE_OPENWEBUI_TOOLS` | `True` | 启用 OpenWebUI 工具 (包括定义工具和内置工具)。 |
| `ENABLE_OPENAPI_SERVER` | `True` | 启用 OpenAPI 工具服务器连接。 |
| `ENABLE_MCP_SERVER` | `True` | 启用直接 MCP 客户端连接 (推荐)。 |
| `ENABLE_OPENWEBUI_SKILLS` | `True` | 开启与 OpenWebUI **工作区 > Skills** 的双向同步桥接。 |
| `OPENWEBUI_SKILLS_SHARED_DIR` | `/app/backend/data/cache/copilot-openwebui-skills` | OpenWebUI skills 转换后的共享缓存目录。 |
| `GITHUB_SKILLS_SOURCE_URL` | `""` | 可选 GitHub tree 地址,用于批量导入 skills例如 anthropic/skills。 |
| `DISABLED_SKILLS` | `""` | 逗号分隔的 skill 名称黑名单(如 `docs-writer,webapp-testing`。 |
| `REASONING_EFFORT` | `medium` | 推理强度low, medium, high。 |
| `SHOW_THINKING` | `True` | 显示模型推理/思考过程。 |
| `INFINITE_SESSION` | `True` | 启用无限会话 (自动上下文压缩)。 |
| `MAX_MULTIPLIER` | `1.0` | 最大允许的模型计费倍率 (0x 为仅限免费模型)。 |
| `EXCLUDE_KEYWORDS` | `""` | 排除包含这些关键字的模型 (逗号分隔)。 |
| `TIMEOUT` | `300` | 每个流数据块的超时时间 (秒)。 |
| `BYOK_TYPE` | `openai` | BYOK 服务商类型:`openai`, `anthropic`。 |
| `BYOK_BASE_URL` | `""` | BYOK 基础 URL (例如: <https://api.openai.com/v1)。> |
| `BYOK_MODELS` | `""` | BYOK 模型列表 (逗号分隔)。留空则从 API 获取。 |
| `CUSTOM_ENV_VARS` | `""` | 自定义环境变量 (JSON 格式)。 |
| `DEBUG` | `False` | 开启此项以在前端控制台输出详细调试日志。 |
### 2. 用户配置 (个人覆盖)
普通用户可在各自的个人设置中根据需要覆盖以下参数。
| 参数 | 说明 |
| :--- | :--- |
| `GH_TOKEN` | 使用个人的 GitHub Token。 |
| `REASONING_EFFORT` | 个人偏好的推理强度。 |
| `SHOW_THINKING` | 显示模型推理/思考过程。 |
| `MAX_MULTIPLIER` | 最大允许的模型计费倍率覆盖。 |
| `EXCLUDE_KEYWORDS` | 排除包含这些关键字的模型。 |
| `ENABLE_OPENWEBUI_SKILLS` | 启用将当前用户可读的全部已启用 OpenWebUI skills 转换并加载为 SDK `SKILL.md` 目录。 |
| `GITHUB_SKILLS_SOURCE_URL` | 为当前用户会话设置可选 GitHub tree 地址以批量导入 skills。 |
| `DISABLED_SKILLS` | 为当前用户会话禁用指定 skills逗号分隔。 |
| `BYOK_API_KEY` | 使用个人的 OpenAI/Anthropic API Key。 |
| `github_token` | - | GitHub Copilot 官方 Token (如果您有官方订阅且不方便本地登录时填入)。 |
| `llm_base_url` | - | BYOK 模式的基础 URL。填入后将绕过 GitHub 官方服务。 |
| `llm_api_key` | - | BYOK 模式的 API 密钥。 |
| `llm_model_id` | `gpt-4o` | 使用的模型 ID (官方、BYOK 均适用)。 |
| `workspace_root` | `./copilot_workspaces` | 所有会话沙盒的根目录。 |
| `skills_directory` | `./copilot_skills` | 自定义 SDK 技能文件夹所在的目录。 |
| `show_status` | `True` | 是否在 UI 显示 Agent 的实时运行状态和思考过程。 |
| `enable_infinite_session` | `True` | 是否开启自动上下文压缩和 TODO 列表持久化。 |
| `enable_html_artifacts` | `True` | 是否允许 Agent 生成并实时预览 HTML 应用。 |
| `enable_rich_ui` | `True` | 是否启用进度条和增强型工具调用面板。 |
---
### 🌊 细粒度反馈与流畅体验 (Fluid UX)
## 🤝 支持 (Support)
彻底告别复杂任务执行过程中的“卡顿”感:
- **🔄 实时状态气泡**: 将 SDK 内部事件(如 `turn_start`, `compaction`, `subagent_started`)直接映射为 OpenWebUI 的状态栏信息。
- **🧭 分阶段状态描述增强**: 状态栏会明确显示处理阶段(处理中、技能触发、工具执行、工具完成/失败、发布中、任务完成)。
- **⏱️ 长任务心跳提示**: 长时间处理中会周期性显示“仍在处理中(已耗时 X 秒)”,避免用户误判为卡死。
- **📈 工具执行进度追踪**: 长耗时工具(如代码分析)会在状态栏实时显示进度百分比及当前子任务描述。
- **⚡ 即时响应反馈**: 从响应开始第一秒即显示“助手正在处理您的请求...”,减少等待空窗感。
如果这个插件对你有帮助,欢迎到 [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) 点个 Star这将是我持续改进的动力感谢支持。
---
### 🛡️ 智能版本兼容
## ⚠️ 故障排除 (Troubleshooting)
插件会自动根据您的 OpenWebUI 版本调整功能集:
- **v0.8.0+**: 开启 Rich UI、实时状态气泡及集成 HTML 预览
- **旧版本**: 自动回退至标准 Markdown 代码块模式,确保最大稳定性。
---
## 🎯 典型应用场景 (Use Cases)
- **📁 全自主仓库维护**: Agent 在隔离工作区内自动分析代码、运行测试并应用补丁。
- **📊 深度财务数据审计**: 直接通过 Python 加载 Excel/CSV 原始数据(绕过 RAG生成图表并实时预览。
- **📝 长任务项目管理**: 自动拆解复杂任务并持久化 TOD·O 进度,跨会话跟踪执行状态。
---
## ⭐ 支持与交流 (Support)
如果这个插件对您有所帮助,请在 [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions) 项目上点个 **Star** 💫,这是对我最大的鼓励。
---
## 🚀 安装与配置 (Installation)
### 1) 导入函数
1. 打开 OpenWebUI前往 **工作区** -> **函数**
2. 点击 **+** (创建函数),完整粘贴 `github_copilot_sdk.py` 的内容。
3. 点击保存并确保已启用。
### 2) 获取 Token (Get Token)
1. 访问 [GitHub Token 设置](https://github.com/settings/tokens?type=beta)。
2. 创建 **Fine-grained token**,授予 **Account permissions** -> **Copilot Requests** 访问权限。
3. 将生成的 Token 填入插件的 `GH_TOKEN` 配置项中。
### 3) 认证配置要求(必填)
你必须至少配置以下一种凭据:
- `GH_TOKEN`GitHub Copilot 官方订阅路径),或
- `BYOK_API_KEY`OpenAI/Anthropic 自带 Key 路径)。
如果两者都未配置,模型列表将不会出现。
### 4) 配套插件 (强烈推荐)
为了获得最佳的文件处理体验,请安装 [GitHub Copilot SDK Files Filter](https://openwebui.com/posts/403a62ee-a596-45e7-be65-fab9cc249dd6)。
---
### 📤 增强型发布工具与交互式组件
`publish_file_from_workspace` 现采用更清晰、可落地的交付规范:
- **Artifacts 模式(`artifacts`,默认)**:返回 `[Preview]` + `[Download]`,并可附带 `html_embed`,在 ```html 代码块中直接渲染。
- **Rich UI 模式(`richui`**:仅返回 `[Preview]` + `[Download]`,由发射器自动触发集成式预览(消息中不输出 iframe 代码块)。
- **📄 PDF 安全交付规则**:仅输出 Markdown 链接(可用时为 `[Preview]` + `[Download]`)。**禁止通过 iframe/html 方式嵌入 PDF。**
- **⚡ 稳定双通道发布**:在本地与对象存储后端下,保持交互预览与持久下载链接一致可用。
- **✅ 状态集成**:通过 OpenWebUI 状态栏实时反馈发布进度与完成状态。
- **📘 发布工具指南GitHub**[publish_file_from_workspace 工具指南(中文)](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/pipes/github-copilot-sdk/PUBLISH_FILE_FROM_WORKSPACE_CN.md)
---
### 🧩 OpenWebUI Skills 桥接与 `manage_skills` 工具
SDK 现在具备与 OpenWebUI **工作区 > Skills** 的双向同步能力:
- **🔄 自动同步**: 每次请求时,前端定义的技能会自动作为 `SKILL.md` 文件夹同步至 SDK 共享缓存Agent 可直接调用。
- **🛠️ `manage_skills` 工具**: 内置专业工具,赋予 Agent (或用户) 绝对的技能管理权。
- `list`: 列出所有已安装技能及描述。
- `install`: 从 GitHub URL (自动转换归档链接) 或直接从 `.zip`/`.tar.gz` 安装。
- `create`: 从当前会话内容创建新技能目录,支持写入 `SKILL.md` 及辅助资源文件 (脚本、模板)。
- `edit`: 更新现有技能文件夹。
- `delete`: 原子化删除本地目录及关联的数据库条目,防止僵尸技能复活。
- **📁 完整的文件夹支持**: 不同于数据库中单文件存储SDK 会加载技能的**整个目录**。这使得技能可以携带二进制脚本、数据文件或复杂模板。
- **🌐 持久化共享缓存**: 技能存储在 `OPENWEBUI_SKILLS_SHARED_DIR/shared/`,跨会话及容器重启持久存在。
- **📚 技能完整文档GitHub**: [manage_skills 工具指南(中文)](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/pipes/github-copilot-sdk/SKILLS_MANAGER_CN.md) | [Skills Best Practices中文](https://github.com/Fu-Jie/openwebui-extensions/blob/main/plugins/pipes/github-copilot-sdk/SKILLS_BEST_PRACTICES_CN.md)
---
## 📋 常见问题与依赖 (Troubleshooting)
- **Agent 无法识别文件?**: 请确保已安装并启用了 Files Filter 插件,否则原始文件会被 RAG 干扰。
- **看不到状态更新或 TODO 进度条?**: 状态气泡会覆盖处理/工具阶段;而 TODO 进度条仅在 Agent 使用 `update_todo` 工具(通常是复杂任务)时出现。
- **依赖安装**: 本管道会自动管理 `github-copilot-sdk` (Python 包) 并优先直接使用内置的二进制 CLI无需手动干预。
---
## 更新日志 (Changelog)
完整历史记录请见 GitHub: [OpenWebUI Extensions](https://github.com/Fu-Jie/openwebui-extensions)
- **工具无法使用?** 请检查是否安装了 `github-copilot-sdk`
- **文件找不到?** 确保已启用配套的 `Files Filter` 插件。
- **BYOK 报错?** 确认 `llm_base_url` 包含协议前缀(如 `https://`)且模型 ID 准确无误
- **卡在 "Thinking..."?** 检查后端网络连接,流式传输可能受某些代理拦截。

View File

@@ -4,8 +4,8 @@ author: Fu-Jie
author_url: https://github.com/Fu-Jie/openwebui-extensions
funding_url: https://github.com/open-webui
openwebui_id: ce96f7b4-12fc-4ac3-9a01-875713e69359
description: Integrate GitHub Copilot SDK. Supports dynamic models, multi-turn conversation, streaming, multimodal input, infinite sessions, bidirectional OpenWebUI Skills bridge, and manage_skills tool.
version: 0.9.0
description: A powerful Agent SDK integration for OpenWebUI. It deeply bridges GitHub Copilot SDK with OpenWebUI's ecosystem, enabling the Agent to autonomously perform intent recognition, web search, and context compaction. It seamlessly reuses your existing Tools, MCP servers, OpenAPI servers, and Skills for a professional, full-featured experience.
version: 0.9.1
requirements: github-copilot-sdk==0.1.25
"""
@@ -153,9 +153,9 @@ BASE_GUIDELINES = (
"3. **Interactive Artifacts (HTML)**: **Premium Delivery Protocol**: For web applications, you MUST perform two actions:\n"
" - 1. **Persist**: Create the file in the workspace (e.g., `index.html`) for project structure.\n"
" - 2. **Publish & Embed**: Call `publish_file_from_workspace(filename='your_file.html')`. This will automatically trigger the **Premium Experience** by directly embedding the interactive component using the action-style return.\n"
" - **CRITICAL ANTI-INLINE RULE**: Never output your own raw HTML code blocks directly in your chat response when creating a web app, dashboard, or visual artifact. YOU MUST ALWAYS persist the HTML to a file and call `publish_file_from_workspace` to deliver it.\n"
" - **CRITICAL ANTI-INLINE RULE**: Never output your *own* raw HTML source code directly in the chat. You MUST ALWAYS persist the HTML to a file and call `publish_file_from_workspace`.\n"
" - **CRITICAL**: When using this protocol in **Rich UI mode** (`embed_type='richui'`), **DO NOT** output the raw HTML code in a code block. Provide ONLY the **[Preview]** and **[Download]** links returned by the tool. The interactive embed will appear automatically after your message finishes.\n"
" - **Artifacts mode** (`embed_type='artifacts'`): You MUST provide the **[Preview]** and **[Download]** links, AND then you MUST output the provided `html_embed` (the iframe) wrapped within a ```html code block to enable the interactive preview.\n"
" - **Artifacts mode** (`embed_type='artifacts'`): You MUST provide the **[Preview]** and **[Download]** links. DO NOT output HTML code block. The system will automatically append the HTML visualization to the chat string.\n"
" - **Process Visibility**: While raw code is often replaced by links/frames, you SHOULD provide a **very brief Markdown summary** of the component's structure or key features (e.g., 'Generated login form with validation') before publishing. This keeps the user informed of the 'processing' progress.\n"
" - **Game/App Controls**: If your HTML includes keyboard controls (e.g., arrow keys, spacebar for games), you MUST include `event.preventDefault()` in your `keydown` listeners to prevent the parent browser page from scrolling.\n"
"4. **Media & Files**: ALWAYS embed generated images, GIFs, and videos directly using `![caption](url)`. Supported formats like PNG, JPG, GIF, MOV, and MP4 should be shown as visual assets. Never provide plain text links for visual media.\n"
@@ -165,9 +165,15 @@ BASE_GUIDELINES = (
" - **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. 2. **Publish**: Call `publish_file_from_workspace`. 3. **Response Structure**:\n"
" - **Strict Link Validity Rule (CRITICAL)**: You are FORBIDDEN to fabricate, guess, or handcraft any preview/download URL. Links MUST come directly from a successful `publish_file_from_workspace` tool result in the same turn.\n"
" - **Failure Handling**: If publish fails or no tool result is returned, DO NOT output any fake/placeholder link. Instead, explicitly report publish failure and ask to retry publish.\n"
" - **No Pre-Publish Linking**: Never output links before running publish. 'Create file' alone is NOT enough to produce a valid user-facing link.\n"
" - **Allowed Link Formats (Whitelist)**: You MUST only output links matching these exact patterns from tool output: `/api/v1/files/{file_id}/content` (download/content) and `/api/v1/files/{file_id}/content/html` (HTML preview). Absolute form is also valid only when it is exactly `{base_url}` + one of the two paths.\n"
" - **Invalid Link Examples (Forbidden)**: Any handcrafted variants such as `/files/...`, `/api/files/...`, `/api/v1/file/...`, missing `/content`, manually appended custom routes, or local-workspace style paths like `/c/...`, `./...`, `../...`, `file://...` are INVALID and MUST NOT be output.\n"
" - **Auto-Correction Rule**: If you generated a non-whitelisted link (including `/c/...`), you MUST discard it, run/confirm `publish_file_from_workspace`, and only output the returned whitelisted URL.\n"
" - **For PDF files**: You MUST output ONLY Markdown links from the tool output (preview + download). **CRITICAL: NEVER output iframe/html_embed for PDF.**\n"
" - **For HTML files**: choose mode by complexity/environment. **Artifacts mode** (`embed_type='artifacts'`): output [Preview]/[Download], then MISSION-CRITICAL: output the provided `html_embed` value wrapped EXACTLY within a ```html code block. **Rich UI mode** (`embed_type='richui'`): output ONLY [Preview]/[Download]; do NOT output iframe/html block because Rich UI will render automatically via emitter.\n"
" - **URL Format**: You MUST use the **ABSOLUTE URLs** provided in the tool output. NEVER modify them.\n"
" - **For HTML files**: Choose mode by complexity. **Artifacts mode** (`embed_type='artifacts'`): REQUIRED for dashboards, reports, and large/long UI since it has unlimited height. Output ONLY [Preview]/[Download]; do NOT output any iframe/html block because the protocol will automatically append the html code block via emitter. **Rich UI mode** (`embed_type='richui'`): For small widgets ONLY. If you MUST use Rich UI for long content, you MUST add a clickable 'Full Screen' button inside your HTML design to allow expanding. Output ONLY [Preview]/[Download]; do NOT output HTML block because Rich UI will render automatically via emitter.\n"
" - **URL Format**: You MUST use the **ABSOLUTE URLs** provided in the tool output, copied verbatim. NEVER modify, concatenate, or reconstruct them manually.\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"
@@ -181,6 +187,7 @@ BASE_GUIDELINES = (
" - **Clarification**: Only ask questions if the request is ambiguous or carries high risk (e.g., destructive actions).\n"
" - **Goal**: Minimize user friction. Deliver results, not questions.\n"
"9. **Large Output Management**: If a tool execution output is truncated or saved to a temporary file (e.g., `/tmp/...`), DO NOT worry. The system will automatically move it to your workspace and notify you of the new filename. You can then read it directly.\n"
"10. **Workspace Visibility Hint**: When the user likely wants to inspect workspace files (e.g., asks to view files/directories/current workspace), first provide a brief workspace status summary including the current isolated workspace path and a concise directory snapshot (such as top entries) before deeper operations.\n"
)
# Sensitive extensions only for Administrators
@@ -425,8 +432,8 @@ class Pipe:
"en-US": {
"status_conn_est": "Connection established, waiting for response...",
"status_reasoning_inj": "Reasoning Effort injected: {effort}",
"status_assistant_start": "Assistant is starting to think...",
"status_assistant_processing": "Assistant is processing your request...",
"status_assistant_start": "Agent is starting to think...",
"status_assistant_processing": "Agent is processing your request...",
"status_still_working": "Still processing... ({seconds}s elapsed)",
"status_skill_invoked": "Detected and using skill: {skill}",
"status_tool_using": "Using tool: {name}...",
@@ -434,8 +441,8 @@ class Pipe:
"status_tool_done": "Tool completed: {name}",
"status_tool_failed": "Tool failed: {name}",
"status_subagent_start": "Invoking sub-agent: {name}",
"status_compaction_start": "Optimizing session context...",
"status_compaction_complete": "Context optimization complete.",
"status_compaction_start": "Compacting session context...",
"status_compaction_complete": "Context compaction complete.",
"status_publishing_file": "Publishing artifact: {filename}",
"status_task_completed": "Task completed.",
"status_session_error": "Processing failed: {error}",
@@ -450,8 +457,8 @@ class Pipe:
"zh-CN": {
"status_conn_est": "已建立连接,等待响应...",
"status_reasoning_inj": "已注入推理级别:{effort}",
"status_assistant_start": "助手开始思考...",
"status_assistant_processing": "助手正在处理您的请求...",
"status_assistant_start": "Agent 开始思考...",
"status_assistant_processing": "Agent 正在处理您的请求...",
"status_still_working": "仍在处理中...(已耗时 {seconds} 秒)",
"status_skill_invoked": "已发现并使用技能:{skill}",
"status_tool_using": "正在使用工具:{name}...",
@@ -459,8 +466,8 @@ class Pipe:
"status_tool_done": "工具已完成:{name}",
"status_tool_failed": "工具执行失败:{name}",
"status_subagent_start": "正在调用子代理:{name}",
"status_compaction_start": "正在优化会话上下文...",
"status_compaction_complete": "上下文优化完成。",
"status_compaction_start": "正在压缩会话上下文...",
"status_compaction_complete": "上下文压缩完成。",
"status_publishing_file": "正在发布成果物:{filename}",
"status_task_completed": "任务已完成。",
"status_session_error": "处理失败:{error}",
@@ -475,14 +482,14 @@ class Pipe:
"zh-HK": {
"status_conn_est": "已建立連接,等待響應...",
"status_reasoning_inj": "已注入推理級別:{effort}",
"status_assistant_start": "助手開始思考...",
"status_assistant_processing": "助手正在處理您的請求...",
"status_assistant_start": "Agent 開始思考...",
"status_assistant_processing": "Agent 正在處理您的請求...",
"status_skill_invoked": "已發現並使用技能:{skill}",
"status_tool_using": "正在使用工具:{name}...",
"status_tool_progress": "工具進度:{name} ({progress}%) {msg}",
"status_subagent_start": "正在調用子代理:{name}",
"status_compaction_start": "正在優化會話上下文...",
"status_compaction_complete": "上下文優化完成。",
"status_compaction_start": "正在壓縮會話上下文...",
"status_compaction_complete": "上下文壓縮完成。",
"status_publishing_file": "正在發布成果物:{filename}",
"status_no_skill_invoked": "本輪未觸發任何技能DEBUG",
"debug_agent_working_in": "Agent 工作目錄: {path}",
@@ -495,14 +502,14 @@ class Pipe:
"zh-TW": {
"status_conn_est": "已建立連接,等待響應...",
"status_reasoning_inj": "已注入推理級別:{effort}",
"status_assistant_start": "助手開始思考...",
"status_assistant_processing": "助手正在處理您的請求...",
"status_assistant_start": "Agent 開始思考...",
"status_assistant_processing": "Agent 正在處理您的請求...",
"status_skill_invoked": "已發現並使用技能:{skill}",
"status_tool_using": "正在使用工具:{name}...",
"status_tool_progress": "工具進度:{name} ({progress}%) {msg}",
"status_subagent_start": "正在調用子代理:{name}",
"status_compaction_start": "正在優化會話上下文...",
"status_compaction_complete": "上下文優化完成。",
"status_compaction_start": "正在壓縮會話上下文...",
"status_compaction_complete": "上下文壓縮完成。",
"status_publishing_file": "正在發布成果物:{filename}",
"status_no_skill_invoked": "本輪未觸發任何技能DEBUG",
"debug_agent_working_in": "Agent 工作目錄: {path}",
@@ -515,7 +522,11 @@ class Pipe:
"ja-JP": {
"status_conn_est": "接続が確立されました。応答を待っています...",
"status_reasoning_inj": "推論レベルが注入されました:{effort}",
"status_assistant_start": "Agent が考え始めました...",
"status_assistant_processing": "Agent がリクエストを処理しています...",
"status_skill_invoked": "スキルが検出され、使用されています:{skill}",
"status_compaction_start": "セッションコンテキストを圧縮しています...",
"status_compaction_complete": "コンテキストの圧縮が完了しました。",
"status_publishing_file": "アーティファクトを公開中:{filename}",
"status_no_skill_invoked": "このターンではスキルは呼び出されませんでした (DEBUG)",
"debug_agent_working_in": "エージェントの作業ディレクトリ: {path}",
@@ -528,7 +539,11 @@ class Pipe:
"ko-KR": {
"status_conn_est": "연결이 설정되었습니다. 응답을 기다리는 중...",
"status_reasoning_inj": "추론 노력이 주입되었습니다: {effort}",
"status_assistant_start": "Agent 가 생각을 시작했습니다...",
"status_assistant_processing": "Agent 가 요청을 처리 중입니다...",
"status_skill_invoked": "스킬이 감지되어 사용 중입니다: {skill}",
"status_compaction_start": "세션 컨텍스트를 압축하는 중...",
"status_compaction_complete": "컨텍스트 압축 완료.",
"status_publishing_file": "아티팩트 게시 중: {filename}",
"status_no_skill_invoked": "이 턴에는 스킬이 호출되지 않았습니다 (DEBUG)",
"debug_agent_working_in": "에이전트 작업 디렉토리: {path}",
@@ -541,7 +556,11 @@ class Pipe:
"fr-FR": {
"status_conn_est": "Connexion établie, en attente de réponse...",
"status_reasoning_inj": "Effort de raisonnement injecté : {effort}",
"status_assistant_start": "L'Agent commence à réfléchir...",
"status_assistant_processing": "L'Agent traite votre demande...",
"status_skill_invoked": "Compétence détectée et utilisée : {skill}",
"status_compaction_start": "Compression du contexte de session...",
"status_compaction_complete": "Compression du contexte terminée.",
"status_publishing_file": "Publication de l'artefact : {filename}",
"status_no_skill_invoked": "Aucune compétence invoquée pour ce tour (DEBUG)",
"debug_agent_working_in": "Agent travaillant dans : {path}",
@@ -554,7 +573,11 @@ class Pipe:
"de-DE": {
"status_conn_est": "Verbindung hergestellt, warte auf Antwort...",
"status_reasoning_inj": "Schlussfolgerungsaufwand injiziert: {effort}",
"status_assistant_start": "Agent beginnt zu denken...",
"status_assistant_processing": "Agent bearbeitet Ihre Anfrage...",
"status_skill_invoked": "Skill erkannt und verwendet: {skill}",
"status_compaction_start": "Sitzungskontext wird komprimiert...",
"status_compaction_complete": "Kontextkomprimierung abgeschlossen.",
"status_publishing_file": "Artifact wird veröffentlicht: {filename}",
"status_no_skill_invoked": "In dieser Runde wurde kein Skill aufgerufen (DEBUG)",
"debug_agent_working_in": "Agent arbeitet in: {path}",
@@ -567,7 +590,11 @@ class Pipe:
"it-IT": {
"status_conn_est": "Connessione stabilita, in attesa di risposta...",
"status_reasoning_inj": "Sforzo di ragionamento iniettato: {effort}",
"status_assistant_start": "L'Agent sta iniziando a pensare...",
"status_assistant_processing": "L'Agent sta elaborando la tua richiesta...",
"status_skill_invoked": "Skill rilevata e utilizzata: {skill}",
"status_compaction_start": "Compattazione del contesto della sessione...",
"status_compaction_complete": "Compattazione del contesto completata.",
"status_publishing_file": "Pubblicazione dell'artefatto: {filename}",
"status_no_skill_invoked": "Nessuna skill invocata in questo turno (DEBUG)",
"debug_agent_working_in": "Agente al lavoro in: {path}",
@@ -580,7 +607,11 @@ class Pipe:
"es-ES": {
"status_conn_est": "Conexión establecida, esperando respuesta...",
"status_reasoning_inj": "Esfuerzo de razonamiento inyectado: {effort}",
"status_assistant_start": "El Agent está empezando a pensar...",
"status_assistant_processing": "El Agent está procesando su solicitud...",
"status_skill_invoked": "Habilidad detectada y utilizada: {skill}",
"status_compaction_start": "Compactando el contexto de la sesión...",
"status_compaction_complete": "Compactación del contexto completada.",
"status_publishing_file": "Publicando artefacto: {filename}",
"status_no_skill_invoked": "No se invocó ninguna habilidad en este turno (DEBUG)",
"debug_agent_working_in": "Agente trabajando en: {path}",
@@ -593,7 +624,11 @@ class Pipe:
"vi-VN": {
"status_conn_est": "Đã thiết lập kết nối, đang chờ phản hồi...",
"status_reasoning_inj": "Nỗ lực suy luận đã được đưa vào: {effort}",
"status_assistant_start": "Agent bắt đầu suy nghĩ...",
"status_assistant_processing": "Agent đang xử lý yêu cầu của bạn...",
"status_skill_invoked": "Kỹ năng đã được phát hiện và sử dụng: {skill}",
"status_compaction_start": "Đang nén ngữ cảnh phiên...",
"status_compaction_complete": "Nén ngữ cảnh hoàn tất.",
"status_publishing_file": "Đang xuất bản thành phẩm: {filename}",
"status_no_skill_invoked": "Không có kỹ năng nào được gọi trong lượt này (DEBUG)",
"debug_agent_working_in": "Agent đang làm việc tại: {path}",
@@ -606,7 +641,11 @@ class Pipe:
"id-ID": {
"status_conn_est": "Koneksi terjalin, menunggu respons...",
"status_reasoning_inj": "Upaya penalaran dimasukkan: {effort}",
"status_assistant_start": "Agen mulai berpikir...",
"status_assistant_processing": "Agen sedang memproses permintaan Anda...",
"status_skill_invoked": "Keahlian terdeteksi và digunakan: {skill}",
"status_compaction_start": "Memadatkan konteks sesi...",
"status_compaction_complete": "Pemadatan konteks selesai.",
"status_publishing_file": "Menerbitkan artefak: {filename}",
"status_no_skill_invoked": "Tidak ada keahlian yang dipanggil dalam giliran ini (DEBUG)",
"debug_agent_working_in": "Agen bekerja di: {path}",
@@ -619,7 +658,11 @@ class Pipe:
"ru-RU": {
"status_conn_est": "Соединение установлено, ожидание ответа...",
"status_reasoning_inj": "Уровень рассуждения внедрен: {effort}",
"status_assistant_start": "Agent начинает думать...",
"status_assistant_processing": "Agent обрабатывает ваш запрос...",
"status_skill_invoked": "Обнаружен и используется навык: {skill}",
"status_compaction_start": "Сжатие контекста сеанса...",
"status_compaction_complete": "Сжатие контекста завершено.",
"status_publishing_file": "Публикация файла: {filename}",
"status_no_skill_invoked": "На этом шаге навыки не вызывались (DEBUG)",
"debug_agent_working_in": "Рабочий каталог Агента: {path}",
@@ -923,9 +966,9 @@ class Pipe:
return final_tools
# 4. Extract chat-level tool selection (P4: user selection from Chat UI)
chat_tool_ids = None
if __metadata__ and isinstance(__metadata__, dict):
chat_tool_ids = __metadata__.get("tool_ids") or None
chat_tool_ids = self._normalize_chat_tool_ids(
__metadata__.get("tool_ids") if isinstance(__metadata__, dict) else None
)
# 5. Load OpenWebUI tools dynamically (always fresh, no cache)
openwebui_tools = await self._load_openwebui_tools(
@@ -1012,8 +1055,8 @@ class Pipe:
default="artifacts",
description=(
"Rendering style for HTML files. For PDF files, embedding is disabled and you MUST only provide preview/download Markdown links from tool output. "
"Use 'artifacts' for HTML (Default: output html_embed iframe inside a ```html code block; no height limit). "
"Use 'richui' for HTML (emitter-based integrated preview). DO NOT output html_embed in richui mode; it is rendered automatically. "
"Use 'artifacts' for large UI, dashboards, or long content (Default: output html_embed iframe inside a ```html code block; no height limit). "
"Use 'richui' ONLY for small/compact widgets (emitter-based integrated preview). DO NOT output html_embed in richui mode. If richui is used for long content, you MUST add a 'Full Screen' expansion button in the HTML logic. "
"Only 'artifacts' and 'richui' are supported."
),
)
@@ -1191,7 +1234,7 @@ class Pipe:
if embed_type == "richui":
hint += "\n\nCRITICAL: You are in 'richui' mode. DO NOT output an HTML code block or iframe in your message. Just output the links above."
elif embed_type == "artifacts":
hint += "\n\nIMPORTANT: You are in 'artifacts' mode. You MUST wrap the provided 'html_embed' (the iframe) inside a ```html code block in your response to enable the interactive preview."
hint += "\n\nIMPORTANT: You are in 'artifacts' mode. DO NOT output an HTML code block in your message. The system will automatically inject it after you finish."
elif has_preview:
hint = self._get_translation(
user_lang,
@@ -1224,43 +1267,78 @@ class Pipe:
}
if has_preview and view_url:
result_dict["view_url"] = view_url
if is_html and embed_type == "artifacts":
# Artifacts mode: standard iframe for the AI to output directly (Infinite height)
iframe_html = (
f'<iframe src="{view_url}" '
f'style="width:100%; height:100vh; min-height:600px; border:none; border-radius:12px; '
f'box-shadow: var(--shadow-lg);"></iframe>'
)
result_dict["html_embed"] = iframe_html
# Note: We do NOT add to pending_embeds. The AI will output this in the message.
elif embed_type == "richui":
# In richui mode, we physically remove html_embed to prevent the AI from outputting it
# The system will handle the rendering via emitter
pass
# 6. Premium Rich UI Experience for HTML only (Direct Embed via emitter)
# We emit events directly ONLY IF embed_type is 'richui'.
# Note: Emission is now delayed until session.idle to avoid UI flicker and ensure reliability.
if is_html and embed_type == "richui" and rich_ui_supported:
# Premium Experience for HTML only (Direct Embed via emitter)
# Emission is delayed until session.idle to avoid UI flicker and ensure reliability.
if is_html and rich_ui_supported:
try:
# For Rich UI Integrated view, we pass a clean iframe.
# We use 60vh directly to avoid nested iframe height collapses.
embed_content = (
f'<iframe src="{view_url}" '
f'style="width:100%; height:60vh; min-height:400px; border:none; border-radius:12px; '
f'box-shadow: var(--shadow-lg);"></iframe>'
# For BOTH Rich UI and Artifacts Mode, OpenWebUI expects the raw HTML of the component itself
embed_content = await asyncio.to_thread(
lambda: target_path.read_text(
encoding="utf-8", errors="replace"
)
)
if pending_embeds is not None:
pending_embeds.append(
{
"filename": safe_filename,
"content": embed_content,
"type": "richui",
}
if embed_type == "richui":
pending_embeds.append(
{
"filename": safe_filename,
"content": embed_content,
"type": "richui",
}
)
logger.info(
f"[Copilot] Queued richui embed for '{safe_filename}', pending_embeds len={len(pending_embeds)}"
)
if __event_emitter__:
await __event_emitter__(
{
"type": "status",
"data": {
"description": f"📦 Queued richui embed: {safe_filename} (queue={len(pending_embeds)})",
"done": True,
},
}
)
elif embed_type == "artifacts":
artifacts_content = f"\n```html\n{embed_content}\n```\n"
pending_embeds.append(
{
"filename": safe_filename,
"content": artifacts_content,
"type": "artifacts",
}
)
logger.info(
f"[Copilot] Queued artifacts embed for '{safe_filename}' (content={len(artifacts_content)}, queue={len(pending_embeds)})"
)
if __event_emitter__:
await __event_emitter__(
{
"type": "status",
"data": {
"description": f"📦 Queued artifacts embed: {safe_filename} (content={len(artifacts_content)}, queue={len(pending_embeds)})",
"done": True,
},
}
)
else:
logger.warning(
f"[Copilot] pending_embeds is None! Cannot queue embed for '{safe_filename}'"
)
if __event_emitter__:
await __event_emitter__(
{
"type": "status",
"data": {
"description": f"⚠️ pending_embeds is None for {safe_filename}",
"done": True,
},
}
)
except Exception as e:
logger.error(f"Failed to prepare Rich UI embed: {e}")
logger.error(f"Failed to prepare HTML embed: {e}")
return result_dict
@@ -2155,11 +2233,12 @@ class Pipe:
return []
# P4: Chat tool_ids whitelist — only active when user explicitly selected tools
if chat_tool_ids:
chat_tool_ids_set = set(chat_tool_ids)
selected_custom_tool_ids = self._extract_selected_custom_tool_ids(chat_tool_ids)
if selected_custom_tool_ids:
chat_tool_ids_set = set(selected_custom_tool_ids)
filtered = [tid for tid in tool_ids if tid in chat_tool_ids_set]
await self._emit_debug_log(
f"[Tools] tool_ids whitelist active: {len(tool_ids)}{len(filtered)} (selected: {chat_tool_ids})",
f"[Tools] custom tool_ids whitelist active: {len(tool_ids)}{len(filtered)} (selected: {selected_custom_tool_ids})",
__event_call__,
)
tool_ids = filtered
@@ -2249,6 +2328,30 @@ class Pipe:
except Exception:
pass
# Force web_search enabled when OpenWebUI tools are enabled,
# regardless of request feature flags, model meta defaults, or UI toggles.
model_info = (
model_dict.get("info") if isinstance(model_dict, dict) else None
)
if isinstance(model_info, dict):
model_meta = model_info.get("meta")
if not isinstance(model_meta, dict):
model_meta = {}
model_info["meta"] = model_meta
builtin_meta = model_meta.get("builtinTools")
if not isinstance(builtin_meta, dict):
builtin_meta = {}
builtin_meta["web_search"] = True
model_meta["builtinTools"] = builtin_meta
# Force feature selection to True for web_search to bypass UI session toggles
if isinstance(body, dict):
features = body.get("features")
if not isinstance(features, dict):
features = {}
body["features"] = features
features["web_search"] = True
# Get builtin tools
# Code interpreter is STRICT opt-in: only enabled when request
# explicitly sets feature code_interpreter=true. Missing means disabled.
@@ -2345,6 +2448,13 @@ class Pipe:
converted_tools = []
for tool_name, t_dict in tools_dict.items():
if isinstance(tool_name, str) and tool_name.startswith("_"):
if self.valves.DEBUG:
await self._emit_debug_log(
f"[Tools] Skip private tool: {tool_name}",
__event_call__,
)
continue
try:
copilot_tool = self._convert_openwebui_tool_to_sdk(
tool_name,
@@ -2375,6 +2485,7 @@ class Pipe:
return None
mcp_servers = {}
selected_custom_tool_ids = self._extract_selected_custom_tool_ids(chat_tool_ids)
# Read MCP servers directly from DB to avoid stale in-memory cache
connections = self._read_tool_server_connections()
@@ -2405,8 +2516,15 @@ class Pipe:
)
continue
# P4: chat_tool_ids whitelist — if user selected tools, only include matching servers
if chat_tool_ids and f"server:{raw_id}" not in chat_tool_ids:
# P4: chat tool whitelist for MCP servers
# OpenWebUI MCP tool IDs use "server:mcp:{id}" (not just "server:{id}").
# Only enforce MCP server filtering when MCP server IDs are explicitly selected.
selected_mcp_server_ids = {
tid[len("server:mcp:") :]
for tid in selected_custom_tool_ids
if isinstance(tid, str) and tid.startswith("server:mcp:")
}
if selected_mcp_server_ids and raw_id not in selected_mcp_server_ids:
continue
# Sanitize server_id (using same logic as tools)
@@ -2443,13 +2561,18 @@ class Pipe:
function_filter = mcp_config.get("function_name_filter_list", "")
allowed_tools = ["*"]
if function_filter:
if isinstance(function_filter, str):
allowed_tools = [
f.strip() for f in function_filter.split(",") if f.strip()
]
elif isinstance(function_filter, list):
allowed_tools = function_filter
parsed_filter = self._parse_mcp_function_filter(function_filter)
expanded_filter = self._expand_mcp_filter_aliases(
parsed_filter,
raw_server_id=raw_id,
sanitized_server_id=server_id,
)
self._emit_debug_log_sync(
f"[MCP] function_name_filter_list raw={function_filter!r} parsed={parsed_filter} expanded={expanded_filter}",
__event_call__,
)
if expanded_filter:
allowed_tools = expanded_filter
mcp_servers[server_id] = {
"type": "http",
@@ -2595,6 +2718,142 @@ class Pipe:
items = [item.strip() for item in value.split(",")]
return self._dedupe_preserve_order([item for item in items if item])
def _normalize_chat_tool_ids(self, raw_tool_ids: Any) -> List[str]:
"""Normalize chat tool_ids payload to a clean list[str]."""
if not raw_tool_ids:
return []
normalized: List[str] = []
if isinstance(raw_tool_ids, str):
text = raw_tool_ids.strip()
if not text:
return []
if text.startswith("["):
try:
parsed = json.loads(text)
return self._normalize_chat_tool_ids(parsed)
except Exception:
pass
normalized = [p.strip() for p in re.split(r"[,\n;]+", text) if p.strip()]
return self._dedupe_preserve_order(normalized)
if isinstance(raw_tool_ids, (list, tuple, set)):
for item in raw_tool_ids:
if isinstance(item, str):
value = item.strip()
if value:
normalized.append(value)
continue
if isinstance(item, dict):
for key in ("id", "tool_id", "value", "name"):
value = item.get(key)
if isinstance(value, str) and value.strip():
normalized.append(value.strip())
break
return self._dedupe_preserve_order(normalized)
def _extract_selected_custom_tool_ids(self, chat_tool_ids: Any) -> List[str]:
"""Return selected non-builtin tool IDs only."""
normalized = self._normalize_chat_tool_ids(chat_tool_ids)
return self._dedupe_preserve_order(
[
tid
for tid in normalized
if isinstance(tid, str) and not tid.startswith("builtin:")
]
)
def _parse_mcp_function_filter(self, raw_filter: Any) -> List[str]:
"""Parse MCP function filter list from string/list/json into normalized names."""
if not raw_filter:
return []
if isinstance(raw_filter, (list, tuple, set)):
return self._dedupe_preserve_order(
[
str(item).strip().strip('"').strip("'")
for item in raw_filter
if str(item).strip().strip('"').strip("'")
]
)
if isinstance(raw_filter, str):
text = raw_filter.strip()
if not text:
return []
if text.startswith("["):
try:
parsed = json.loads(text)
return self._parse_mcp_function_filter(parsed)
except Exception:
pass
parts = re.split(r"[,\n;,、]+", text)
cleaned: List[str] = []
for part in parts:
value = part.strip().strip('"').strip("'")
if value.startswith("- "):
value = value[2:].strip()
if value:
cleaned.append(value)
return self._dedupe_preserve_order(cleaned)
return []
def _expand_mcp_filter_aliases(
self,
tool_names: List[str],
raw_server_id: str,
sanitized_server_id: str,
) -> List[str]:
"""Expand MCP filter names with common server-prefixed aliases.
Some MCP providers expose namespaced tool names such as:
- github__get_me
- github/get_me
- github.get_me
while admins often configure bare names like `get_me`.
"""
if not tool_names:
return []
prefixes = self._dedupe_preserve_order(
[
str(raw_server_id or "").strip(),
str(sanitized_server_id or "").strip(),
]
)
variants: List[str] = []
for name in tool_names:
clean_name = str(name).strip()
if not clean_name:
continue
# Keep original configured name first.
variants.append(clean_name)
# If admin already provided a namespaced value, keep it as-is only.
if any(sep in clean_name for sep in ("__", "/", ".")):
continue
for prefix in prefixes:
if not prefix:
continue
variants.extend(
[
f"{prefix}__{clean_name}",
f"{prefix}/{clean_name}",
f"{prefix}.{clean_name}",
]
)
return self._dedupe_preserve_order(variants)
def _is_manage_skills_intent(self, text: str) -> bool:
"""Detect whether the user is asking to manage/install skills.
@@ -3498,7 +3757,7 @@ class Pipe:
result = input_data.get("result", "")
# Logic to detect and move large files saved to /tmp
# Pattern: Saved to: /tmp/copilot_result_xxxx.txt
# Pattern: "Saved to: /tmp/copilot_result_xxxx.txt"
import re
import shutil
@@ -4308,9 +4567,9 @@ class Pipe:
)
# P4: Chat tool_ids whitelist — extract once, reuse for both OpenAPI and MCP
chat_tool_ids = None
if __metadata__ and isinstance(__metadata__, dict):
chat_tool_ids = __metadata__.get("tool_ids") or None
chat_tool_ids = self._normalize_chat_tool_ids(
__metadata__.get("tool_ids") if isinstance(__metadata__, dict) else None
)
user_ctx = await self._get_user_context(__user__, __event_call__, __request__)
user_lang = user_ctx["user_language"]
@@ -5275,7 +5534,6 @@ class Pipe:
is_done=True,
)
# Display tool result with improved formatting
# --- TODO Sync Logic (File + DB) ---
if tool_name == "update_todo" and result_type == "success":
try:
@@ -5673,10 +5931,38 @@ class Pipe:
except Exception:
pass
# 2. Emit Rich UI components (richui type)
# 2. Emit UI components (richui or artifacts type)
_idle_dbg = f"IDLE: pending_embeds count={len(pending_embeds) if pending_embeds else 0}, types={[e.get('type') for e in pending_embeds] if pending_embeds else []}"
logger.info(f"[Copilot] {_idle_dbg}")
if __event_call__:
try:
await __event_call__(
{
"type": "execute",
"data": {
"code": f'console.debug("%c[Copilot IDLE] {_idle_dbg}", "color: #f59e0b;");'
},
}
)
except Exception:
pass
if pending_embeds:
for embed in pending_embeds:
if embed.get("type") == "richui":
_embed_dbg = f"Processing embed type='{embed.get('type')}', filename='{embed.get('filename')}', content_len={len(embed.get('content', ''))}"
logger.info(f"[Copilot] IDLE: {_embed_dbg}")
if __event_call__:
try:
await __event_call__(
{
"type": "execute",
"data": {
"code": f'console.debug("%c[Copilot IDLE] {_embed_dbg}", "color: #f59e0b;");'
},
}
)
except Exception:
pass
if embed.get("type") in ["richui", "artifacts"]:
# Status update
await __event_emitter__(
{
@@ -5703,15 +5989,22 @@ class Pipe:
},
}
)
# Standard OpenWebUI Embed Structure: type: "embeds", data: {"embeds": [content]}
await __event_emitter__(
{
"type": "embeds",
"data": {
"embeds": [embed["content"]]
},
}
)
if embed.get("type") == "richui":
# Standard OpenWebUI Embed Structure: type: "embeds", data: {"embeds": [content]}
await __event_emitter__(
{
"type": "embeds",
"data": {
"embeds": [embed["content"]]
},
}
)
elif embed.get("type") == "artifacts":
# Directly yield the markdown block to the response stream.
# This securely appends the code block to the final message,
# tricking OpenWebUI into rendering it as an artifact seamlessly.
yield embed["content"]
# 3. LOCK internal status emission for background tasks
# (Stray Task A from tool.execution_complete will now be discarded)

Some files were not shown because too many files have changed in this diff Show More