Skip to content

Update Skills from CopilotKit Source #32

Update Skills from CopilotKit Source

Update Skills from CopilotKit Source #32

Workflow file for this run

name: Update Skills from CopilotKit Source
on:
schedule:
# Daily at 6am UTC
- cron: "0 6 * * *"
workflow_dispatch:
repository_dispatch:
types: [copilotkit-release]
permissions:
contents: write
pull-requests: write
jobs:
update-skills:
runs-on: ubuntu-latest
outputs:
has-changes: ${{ steps.check-changes.outputs.has-changes }}
new-integrations: ${{ steps.detect-new.outputs.new-integrations }}
strategy2-ran: ${{ steps.detect-new.outputs.strategy2-ran }}
branch-name: ${{ steps.create-pr.outputs.branch-name }}
steps:
- name: Check out skills repo
uses: actions/checkout@v4
with:
path: skills-repo
- name: Check out CopilotKit source repo
uses: actions/checkout@v4
with:
repository: CopilotKit/CopilotKit
path: copilotkit-repo
ref: main
# ──────────────────────────────────────────────
# Strategy 1: Deterministic extraction
# ──────────────────────────────────────────────
- name: "Strategy 1: Extract public API surface"
working-directory: skills-repo
run: |
# PLACEHOLDER: When extraction scripts exist, this step will:
# - Find all @copilotkit/* package index.ts files in copilotkit-repo
# - Grep for 'export' statements to build the public API surface
# - Write results to skills/copilotkit-develop/references/api-surface.md
#
# Example future implementation:
# for pkg in ../copilotkit-repo/packages/copilotkit/*/src/index.ts; do
# grep -E '^export' "$pkg" >> extracted-api.txt
# done
echo "API surface extraction: placeholder (scripts not yet implemented)"
- name: "Strategy 1: Extract error codes and deprecations"
working-directory: skills-repo
run: |
# PLACEHOLDER: When extraction scripts exist, this step will:
# - Grep copilotkit-repo source for error code patterns (CK_ERR_*, error classes)
# - Grep for @deprecated JSDoc annotations
# - Update skills/copilotkit-debug/references/ with error catalog
# - Update skills/copilotkit-upgrade/references/ with deprecation list
#
# Example future implementation:
# grep -rn '@deprecated' ../copilotkit-repo/packages/ > deprecations.txt
# grep -rn 'class.*Error extends' ../copilotkit-repo/packages/ > error-classes.txt
echo "Error/deprecation extraction: placeholder (scripts not yet implemented)"
- name: "Strategy 1: Scan workspace structure"
working-directory: skills-repo
run: |
# PLACEHOLDER: When extraction scripts exist, this step will:
# - Parse Nx workspace configuration from copilotkit-repo
# - Extract package dependency graph
# - Update references with current package structure
#
# Example future implementation:
# cat ../copilotkit-repo/nx.json | jq '.projects' > workspace-structure.json
echo "Workspace structure scan: placeholder (scripts not yet implemented)"
- name: "Strategy 1: Detect new integrations"
id: detect-new
working-directory: skills-repo
run: |
new_list=$(bash scripts/detect-new-integrations.sh \
../copilotkit-repo \
skills/copilotkit-integrations/references/integrations 2>&1) || true
if echo "$new_list" | grep -q "No new integrations detected"; then
echo "No new integrations found."
echo "new-integrations=" >> "$GITHUB_OUTPUT"
echo "strategy2-ran=false" >> "$GITHUB_OUTPUT"
else
echo "New integrations detected:"
echo "$new_list"
# Store as comma-separated for later steps
csv=$(echo "$new_list" | tr '\n' ',' | sed 's/,$//')
echo "new-integrations=${csv}" >> "$GITHUB_OUTPUT"
echo "strategy2-ran=true" >> "$GITHUB_OUTPUT"
fi
# ──────────────────────────────────────────────
# Strategy 2: Claude API escalation for new integrations
# ──────────────────────────────────────────────
- name: "Strategy 2: Generate guides for new integrations"
if: steps.detect-new.outputs.strategy2-ran == 'true'
working-directory: skills-repo
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
IFS=',' read -ra INTEGRATIONS <<< "${{ steps.detect-new.outputs.new-integrations }}"
for integration in "${INTEGRATIONS[@]}"; do
echo "=========================================="
echo "Generating guide for: ${integration}"
echo "=========================================="
example_dir="../copilotkit-repo/examples/integrations/${integration}"
guide_path="skills/copilotkit-integrations/references/integrations/${integration}.md"
# Collect source files from the example directory into a temp file
# (avoids shell variable size limits for large examples)
example_files_tmp=$(mktemp)
while IFS= read -r -d '' file; do
rel_path="${file#${example_dir}/}"
echo "--- FILE: ${rel_path} ---" >> "$example_files_tmp"
head -c 50000 "$file" >> "$example_files_tmp"
echo "" >> "$example_files_tmp"
echo "--- END FILE ---" >> "$example_files_tmp"
done < <(find "$example_dir" -type f \
\( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \
-o -name "*.py" -o -name "*.toml" -o -name "*.yaml" -o -name "*.yml" \
-o -name "*.json" -o -name "*.md" -o -name "*.env.example" \
-o -name "Dockerfile" -o -name "Makefile" \) \
-not -path "*/node_modules/*" \
-not -path "*/.next/*" \
-not -path "*/__pycache__/*" \
-not -path "*/dist/*" \
-not -path "*/.git/*" \
-not -name "package-lock.json" \
-not -name "pnpm-lock.yaml" \
-print0 2>/dev/null)
# Truncate if total is too large (keep under 100KB for API payload)
if [ "$(wc -c < "$example_files_tmp")" -gt 100000 ]; then
head -c 100000 "$example_files_tmp" > "${example_files_tmp}.trunc"
mv "${example_files_tmp}.trunc" "$example_files_tmp"
echo "WARNING: Truncated example files to 100KB for ${integration}"
fi
example_files=$(cat "$example_files_tmp")
rm -f "$example_files_tmp"
if [ -z "$example_files" ]; then
echo "WARNING: No source files found in ${example_dir}, skipping."
continue
fi
# Query CopilotKit docs MCP endpoint for relevant context
doc_context=""
doc_context=$(bash scripts/mcp-query.sh "${integration} integration guide setup" 5 2>/dev/null) || \
echo "WARNING: Could not fetch docs from mcp.copilotkit.ai for ${integration}"
# Read an existing guide as a format reference
reference_guide=$(cat skills/copilotkit-integrations/references/integrations/langgraph.md)
# Build the Claude API request
system_prompt=$(cat <<'SYSPROMPT'
You are a technical writer for CopilotKit. Generate an integration guide in Markdown.
OUTPUT FORMAT RULES:
- Match the structure and tone of the reference guide provided
- Start with a level-1 heading: "# <Framework Name> Integration"
- Include sections: overview, prerequisites, dependencies, project structure, key files walkthrough, how it connects to CopilotKit, running the example
- Use fenced code blocks with language tags
- Be precise and concrete — reference actual file paths and code from the example
TERMINOLOGY RULES (strict):
- Public packages are @copilotkit/* — this is what users install and import
- NEVER reference @copilotkitnext/* packages — these are internal
- NEVER use deprecated v1 names: useCopilotAction (use useFrontendTool), CoAgents (use useAgent), CopilotTextarea (removed), useCopilotReadable (use useAgentContext)
- Current v2 hooks: useAgent, useFrontendTool, useComponent, useAgentContext, useInterrupt, useSuggestions, useHumanInTheLoop
- Current v2 components: CopilotChat, CopilotChatInput, CopilotChatMessageView
- Current v2 runtime: CopilotRuntime, AgentRunner, BuiltInAgent
SOURCE CONSTRAINT:
- ONLY reference code that actually exists in the provided example files
- Do NOT invent configuration, endpoints, or code patterns not present in the source
- If something is unclear from the source, say so rather than guessing
AG-UI PROTOCOL:
- CopilotKit communicates with agent backends via the AG-UI protocol
- AG-UI uses Server-Sent Events (SSE) for streaming agent state, messages, and tool calls
- The frontend useAgent hook connects to the backend agent via AG-UI
- Agent frameworks provide AG-UI adapters/transports that handle the protocol translation
SYSPROMPT
)
# Construct user message in a temp file (can be large)
user_file=$(mktemp)
cat > "$user_file" <<USERMSG
Generate an integration guide for the "${integration}" framework with CopilotKit.
Here is an existing guide for reference (match this format):
${reference_guide}
Here are the source files from the example:
${example_files}
Additional documentation context:
${doc_context:-No additional docs available.}
USERMSG
# Build JSON payload via temp files (avoids argument list too long on both jq and curl)
system_file=$(mktemp)
payload_file=$(mktemp)
echo "$system_prompt" > "$system_file"
jq -n \
--arg model "claude-sonnet-4-20250514" \
--rawfile system "$system_file" \
--rawfile user "$user_file" \
'{
model: $model,
max_tokens: 8192,
system: $system,
messages: [{ role: "user", content: $user }]
}' > "$payload_file"
rm -f "$system_file" "$user_file"
response=$(curl -sf "https://api.anthropic.com/v1/messages" \
-H "x-api-key: ${ANTHROPIC_API_KEY}" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
--max-time 120 \
-d @"$payload_file")
rm -f "$payload_file"
if [ $? -ne 0 ] || [ -z "$response" ]; then
echo "ERROR: Claude API call failed for ${integration}"
continue
fi
# Extract the text content from the response
guide_content=$(echo "$response" | jq -r '.content[0].text // empty')
if [ -z "$guide_content" ]; then
echo "ERROR: Empty response from Claude API for ${integration}"
echo "Raw response: ${response}" | head -c 500
continue
fi
echo "$guide_content" > "$guide_path"
echo "Wrote guide: ${guide_path} ($(wc -l < "$guide_path") lines)"
done
# ──────────────────────────────────────────────
# PR creation
# ──────────────────────────────────────────────
- name: Check for changes
id: check-changes
working-directory: skills-repo
run: |
if git diff --quiet && [ -z "$(git status --porcelain)" ]; then
echo "No changes detected."
echo "has-changes=false" >> "$GITHUB_OUTPUT"
else
echo "Changes detected:"
git status --short
echo "has-changes=true" >> "$GITHUB_OUTPUT"
fi
- name: Bump plugin.json patch version
if: steps.check-changes.outputs.has-changes == 'true'
working-directory: skills-repo
run: |
current=$(jq -r '.version' .claude-plugin/plugin.json)
IFS='.' read -r major minor patch <<< "$current"
new_version="${major}.${minor}.$((patch + 1))"
jq --arg v "$new_version" '.version = $v' .claude-plugin/plugin.json > tmp.json \
&& mv tmp.json .claude-plugin/plugin.json
echo "Version bumped: ${current} -> ${new_version}"
- name: Create branch and open PR
id: create-pr
if: steps.check-changes.outputs.has-changes == 'true'
working-directory: skills-repo
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
branch_name="auto/update-skills"
echo "branch-name=${branch_name}" >> "$GITHUB_OUTPUT"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Check if an existing auto-update PR branch exists
existing_pr=$(gh pr list --head "$branch_name" --state open --json number --jq '.[0].number' || echo "")
if [ -n "$existing_pr" ]; then
# Existing PR — update the branch
git fetch origin "$branch_name" 2>/dev/null || true
git checkout "$branch_name" 2>/dev/null || git checkout -b "$branch_name"
git merge main --no-edit || true
git add -A
git commit -m "Update skills from CopilotKit source ($(date +%Y-%m-%d))" || true
git push origin "$branch_name"
else
# No existing PR — create new branch
git checkout -b "$branch_name"
git add -A
git commit -m "Update skills from CopilotKit source ($(date +%Y-%m-%d))"
git push origin "$branch_name" --force
fi
# Build PR body
changed_files=$(git diff --name-only main..."$branch_name" || git diff --name-only HEAD~1)
strategy2_ran="${{ steps.detect-new.outputs.strategy2-ran }}"
new_integrations="${{ steps.detect-new.outputs.new-integrations }}"
pr_body="## Auto-generated skills update"
pr_body="${pr_body}\n\n**Last updated:** $(date -u +%Y-%m-%dT%H:%M:%SZ)"
pr_body="${pr_body}\n**Trigger:** ${{ github.event_name }}"
pr_body="${pr_body}\n\n### Changes\n\`\`\`\n${changed_files}\n\`\`\`"
if [ "$strategy2_ran" = "true" ]; then
integration_list=$(echo "$new_integrations" | tr ',' '\n' | sed 's/^/- /')
pr_body="${pr_body}\n\n### New integrations (Strategy 2 - Claude-generated)"
pr_body="${pr_body}\nThese guides were generated by Claude and need human review:"
pr_body="${pr_body}\n${integration_list}"
fi
pr_body="${pr_body}\n\n---\n*This PR was auto-generated by the update-skills workflow. Do not auto-merge.*"
pr_body=$(echo -e "$pr_body")
# Determine labels
labels="auto-generated"
if [ "$strategy2_ran" = "true" ]; then
labels="${labels},new-integration"
fi
if [ -n "$existing_pr" ]; then
# Update existing PR body
gh pr edit "$existing_pr" --body "$pr_body"
echo "Updated existing PR #${existing_pr}"
else
# Create new PR
gh pr create \
--title "Update skills from CopilotKit source" \
--body "$pr_body" \
--label "$labels" \
--base main
fi
# ──────────────────────────────────────────────
# Smoke tests (runs on the PR branch)
# ──────────────────────────────────────────────
smoke-tests:
runs-on: ubuntu-latest
needs: update-skills
if: needs.update-skills.outputs.has-changes == 'true'
steps:
- name: Check out skills repo (PR branch)
uses: actions/checkout@v4
with:
ref: ${{ needs.update-skills.outputs.branch-name }}
- name: "Smoke test: Reference docs minimum length"
run: |
exit_code=0
for doc in skills/copilotkit-integrations/references/integrations/*.md; do
[ -f "$doc" ] || continue
lines=$(wc -l < "$doc")
name=$(basename "$doc")
if [ "$lines" -lt 50 ]; then
echo "FAIL: ${name} has only ${lines} lines (minimum 50)"
exit_code=1
else
echo "OK: ${name} (${lines} lines)"
fi
done
exit $exit_code
- name: "Smoke test: Cross-reference SKILL.md hooks against api-surface"
run: |
# Validate that hooks/components mentioned in SKILL.md files
# can be found in the API surface reference (if it exists).
api_surface="skills/copilotkit-develop/references/api-surface.md"
if [ ! -f "$api_surface" ]; then
echo "SKIP: api-surface.md not found yet (placeholder step ran)"
exit 0
fi
exit_code=0
for skill_md in skills/*/SKILL.md; do
[ -f "$skill_md" ] || continue
skill_name=$(basename "$(dirname "$skill_md")")
# Extract hook and component names (useXxx, CopilotXxx patterns)
hooks=$(grep -oE '\b(use[A-Z][a-zA-Z]+|Copilot[A-Z][a-zA-Z]+)\b' "$skill_md" | sort -u)
for hook in $hooks; do
if ! grep -q "$hook" "$api_surface"; then
echo "FAIL: ${skill_name}/SKILL.md references '${hook}' not found in api-surface.md"
exit_code=1
fi
done
done
if [ $exit_code -eq 0 ]; then
echo "OK: All hook/component references validated."
fi
exit $exit_code