feat(github-copilot-sdk): add workspace skills support (v0.9.0) (#51)
* feat(github-copilot-sdk): add workspace skills support - Introduce ENABLE_WORKSPACE_SKILLS valve to enable/disable workspace custom tools discovery - Modify _build_session_config() to auto-load tools from .copilot-skills/ directory - Add workspace_skills_example.py template with 3 working example tools - Update README.md and README_CN.md with Workspace Skills guide and usage examples - Create v0.9.0.md and v0.9.0_CN.md release notes - Sync version to all docs files (index.md, index.zh.md, and main docs) - Bump version from 0.8.0 to 0.9.0 across all 7+ locations * docs: establish temp files handling policy (project-based, not /tmp) - Add TEMP_FILES_POLICY.md guideline for all skills and workflows - Update pr-submitter skill to use .temp/ directory instead of /tmp - Update release-prep skill documentation with temp file convention - Add .temp/ and .build/ entries to .gitignore - Create internal policy memo in /memories/repo/ This policy ensures: - All temporary files stay within project workspace (not system /tmp) - Alignment with OpenWebUI workspace isolation principles - Multi-user safety and cleanup traceability - Consistent handling across all skills and development workflows * fix(terminology): rename 'workspace skills' to 'workspace custom tools' for accuracy The term 'Skills' in Anthropic context refers to instruction-based frameworks (SKILL.md files with YAML frontmatter + markdown), not custom tool functions. Our implementation uses @define_tool decorator to define custom tools that the SDK auto-discovers from .copilot-skills/ directory. These are Tools, not Skills. Changes: - Rename ENABLE_WORKSPACE_SKILLS valve -> ENABLE_WORKSPACE_TOOLS - Update all documentation (README, README_CN, docs, release notes) - Fix section headings and descriptions throughout - Ensure consistent terminology across all files This is a terminology-only change; functionality remains identical. * feat(pipes): release v0.9.0 of GitHub Copilot SDK Pipe - Integrated OpenWebUI Skills Bridge and manage_skills tool - Reinforced status bar stability with session_finalized logic - Added persistent SDK config directory support * docs(pipes): add comprehensive guides and v0.9.0 notes for Copilot SDK - Added skill manager and best practices guides - Added publishing tool documentation - Included v0.9.0 release notes and deployment script - Updated usage guides
This commit is contained in:
260
plugins/pipes/github-copilot-sdk/workspace_skills_example.py
Normal file
260
plugins/pipes/github-copilot-sdk/workspace_skills_example.py
Normal file
@@ -0,0 +1,260 @@
|
||||
"""
|
||||
Workspace Skills Example - Custom Tools for GitHub Copilot SDK
|
||||
|
||||
This file demonstrates how to create custom tools using the @define_tool decorator
|
||||
for use in your workspace's .copilot-skills/ directory.
|
||||
|
||||
USAGE:
|
||||
======
|
||||
1. Create a .copilot-skills/ directory at the root of your workspace:
|
||||
```
|
||||
your-workspace/
|
||||
└── .copilot-skills/
|
||||
├── custom_search.py (copy and modify this example)
|
||||
├── data_processor.py (your custom tools)
|
||||
└── README.md (optional: document your skills)
|
||||
```
|
||||
|
||||
2. Copy this file (or your modified version) to .copilot-skills/
|
||||
|
||||
3. Define your tools using @define_tool decorator:
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
from copilot import define_tool
|
||||
|
||||
class SearchParams(BaseModel):
|
||||
query: str = Field(..., description="Search query")
|
||||
limit: int = Field(default=10, description="Max results")
|
||||
|
||||
@define_tool(description="Search your custom database")
|
||||
async def search_custom_db(query: str, limit: int = 10) -> dict:
|
||||
# Your implementation here
|
||||
return {"results": [...]}
|
||||
|
||||
# Register as tool (tool name will be snake_case of function name)
|
||||
custom_search = define_tool(
|
||||
name="search_custom_db",
|
||||
description="Search your custom database for documents or data",
|
||||
params_type=SearchParams,
|
||||
)(search_custom_db)
|
||||
```
|
||||
|
||||
4. The SDK will automatically discover and register your tools from .copilot-skills/
|
||||
|
||||
5. Use them in your conversation: "Use the search_custom_db tool to find..."
|
||||
|
||||
REQUIREMENTS:
|
||||
=============
|
||||
- Python 3.9+
|
||||
- github-copilot-sdk (v0.1.25+)
|
||||
- Any external dependencies your custom tools need
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from copilot import define_tool
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Example 1: Simple Math Helper Tool
|
||||
# ============================================================================
|
||||
|
||||
@define_tool(description="Perform common mathematical calculations")
|
||||
async def calculate_math(operation: str, value1: float, value2: float = 0) -> dict:
|
||||
"""
|
||||
Performs basic mathematical operations.
|
||||
|
||||
Args:
|
||||
operation: One of 'add', 'subtract', 'multiply', 'divide', 'power', 'sqrt'
|
||||
value1: First number
|
||||
value2: Second number (for binary operations)
|
||||
|
||||
Returns:
|
||||
Dictionary with 'result' and 'operation' keys
|
||||
"""
|
||||
import math
|
||||
|
||||
op_map = {
|
||||
"add": lambda a, b: a + b,
|
||||
"subtract": lambda a, b: a - b,
|
||||
"multiply": lambda a, b: a * b,
|
||||
"divide": lambda a, b: a / b if b != 0 else None,
|
||||
"power": lambda a, b: a ** b,
|
||||
"sqrt": lambda a, _: math.sqrt(a) if a >= 0 else None,
|
||||
}
|
||||
|
||||
result = None
|
||||
if operation in op_map:
|
||||
try:
|
||||
result = op_map[operation](value1, value2)
|
||||
except Exception as e:
|
||||
return {"success": False, "error": str(e)}
|
||||
else:
|
||||
return {"success": False, "error": f"Unknown operation: {operation}"}
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"operation": operation,
|
||||
"value1": value1,
|
||||
"value2": value2,
|
||||
"result": result,
|
||||
}
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Example 2: Text Processing Tool with Parameter Model
|
||||
# ============================================================================
|
||||
|
||||
class TextProcessParams(BaseModel):
|
||||
"""Parameters for text processing operations."""
|
||||
text: str = Field(..., description="The text to process")
|
||||
operation: str = Field(
|
||||
default="count_words",
|
||||
description="Operation: 'count_words', 'to_uppercase', 'to_lowercase', 'reverse', 'count_lines'"
|
||||
)
|
||||
|
||||
|
||||
@define_tool(description="Process and analyze text content")
|
||||
async def process_text(text: str, operation: str = "count_words") -> dict:
|
||||
"""
|
||||
Processes text with various operations.
|
||||
|
||||
Args:
|
||||
text: Input text to process
|
||||
operation: Type of processing to apply
|
||||
|
||||
Returns:
|
||||
Dictionary with processing results
|
||||
"""
|
||||
results = {
|
||||
"operation": operation,
|
||||
"input_length": len(text),
|
||||
"result": None,
|
||||
}
|
||||
|
||||
if operation == "count_words":
|
||||
results["result"] = len(text.split())
|
||||
elif operation == "to_uppercase":
|
||||
results["result"] = text.upper()
|
||||
elif operation == "to_lowercase":
|
||||
results["result"] = text.lower()
|
||||
elif operation == "reverse":
|
||||
results["result"] = text[::-1]
|
||||
elif operation == "count_lines":
|
||||
results["result"] = len(text.split("\n"))
|
||||
else:
|
||||
results["error"] = f"Unknown operation: {operation}"
|
||||
|
||||
return results
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Example 3: Advanced Tool with Complex Return Type
|
||||
# ============================================================================
|
||||
|
||||
class DataAnalysisParams(BaseModel):
|
||||
"""Parameters for data analysis."""
|
||||
data_points: list = Field(..., description="List of numbers to analyze")
|
||||
include_stats: bool = Field(default=True, description="Include statistical analysis")
|
||||
|
||||
|
||||
@define_tool(description="Analyze numerical data and compute statistics")
|
||||
async def analyze_data(data_points: list, include_stats: bool = True) -> dict:
|
||||
"""
|
||||
Analyzes a list of numerical values.
|
||||
|
||||
Args:
|
||||
data_points: List of numbers to analyze
|
||||
include_stats: Whether to include statistical analysis
|
||||
|
||||
Returns:
|
||||
Dictionary with analysis results
|
||||
"""
|
||||
if not data_points or not all(isinstance(x, (int, float)) for x in data_points):
|
||||
return {
|
||||
"error": "data_points must be a non-empty list of numbers",
|
||||
"success": False,
|
||||
}
|
||||
|
||||
results = {
|
||||
"success": True,
|
||||
"count": len(data_points),
|
||||
"min": min(data_points),
|
||||
"max": max(data_points),
|
||||
"sum": sum(data_points),
|
||||
}
|
||||
|
||||
if include_stats:
|
||||
import statistics
|
||||
try:
|
||||
results["mean"] = statistics.mean(data_points)
|
||||
results["median"] = statistics.median(data_points)
|
||||
if len(data_points) > 1:
|
||||
results["stdev"] = statistics.stdev(data_points)
|
||||
except Exception as e:
|
||||
results["stats_error"] = str(e)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Tool Registration (Optional: explicit naming)
|
||||
# ============================================================================
|
||||
# The SDK will auto-discover tools from @define_tool decorated functions.
|
||||
# You can optionally register them explicitly by assigning to variables:
|
||||
|
||||
math_tool = define_tool(
|
||||
name="calculate_math",
|
||||
description="Perform mathematical calculations (add, subtract, multiply, divide, power, sqrt)",
|
||||
params_type=BaseModel, # Can be complex if needed
|
||||
)(calculate_math)
|
||||
|
||||
text_processor = define_tool(
|
||||
name="process_text",
|
||||
description="Process and analyze text (count words, case conversion, etc.)",
|
||||
params_type=TextProcessParams,
|
||||
)(process_text)
|
||||
|
||||
data_analyzer = define_tool(
|
||||
name="analyze_data",
|
||||
description="Analyze numerical data and compute statistics",
|
||||
params_type=DataAnalysisParams,
|
||||
)(analyze_data)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Example: Custom Implementation from Scratch
|
||||
# ============================================================================
|
||||
# If you need more control, implement the Tool class directly:
|
||||
#
|
||||
# from copilot.types import Tool
|
||||
#
|
||||
# async def my_custom_handler(query: str) -> str:
|
||||
# """Your tool logic here."""
|
||||
# return f"Processed: {query}"
|
||||
#
|
||||
# my_tool = Tool(
|
||||
# name="my_custom_tool",
|
||||
# description="My custom tool description",
|
||||
# handler=my_custom_handler,
|
||||
# parameters={} # Add JSON Schema if complex params needed
|
||||
# )
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
"""Test the example tools locally."""
|
||||
import asyncio
|
||||
|
||||
async def main():
|
||||
# Test math tool
|
||||
result1 = await calculate_math("add", 10, 5)
|
||||
print("Math (10 + 5):", result1)
|
||||
|
||||
# Test text processor
|
||||
result2 = await process_text("Hello World", "count_words")
|
||||
print("Text (count words):", result2)
|
||||
|
||||
# Test data analyzer
|
||||
result3 = await analyze_data([1, 2, 3, 4, 5], include_stats=True)
|
||||
print("Data Analysis:", result3)
|
||||
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user