* 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
261 lines
8.2 KiB
Python
261 lines
8.2 KiB
Python
"""
|
|
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())
|