Skip to content

Test message re-ordering fixes #518

Test message re-ordering fixes

Test message re-ordering fixes #518

name: Build and Push Tutorial Agent
on:
workflow_dispatch:
inputs:
rebuild_all:
description: "Rebuild all tutorial agents regardless of changes, this is reserved for maintainers only."
required: false
type: boolean
default: false
pull_request:
paths:
- "examples/tutorials/**"
push:
branches:
- main
paths:
- "examples/tutorials/**"
permissions:
contents: read
packages: write
jobs:
check-permissions:
runs-on: ubuntu-latest
steps:
- name: Check event type and permissions
run: |
if [ "${{ github.event_name }}" != "workflow_dispatch" ]; then
echo "Skipping permission check - not a workflow_dispatch event"
exit 0
fi
echo "Checking maintainer permissions for workflow_dispatch"
- name: Check if user is maintainer
if: ${{ github.event_name == 'workflow_dispatch' }}
uses: actions/github-script@v7
with:
script: |
const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: context.actor
});
const allowedRoles = ['admin', 'maintain'];
if (!allowedRoles.includes(permission.permission)) {
throw new Error(`❌ User ${context.actor} does not have sufficient permissions. Required: ${allowedRoles.join(', ')}. Current: ${permission.permission}`);
}
find-agents:
runs-on: ubuntu-latest
needs: [check-permissions]
outputs:
agents: ${{ steps.get-agents.outputs.agents }}
all_agents: ${{ steps.get-agents.outputs.all_agents }}
has_agents: ${{ steps.get-agents.outputs.has_agents }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Fetch full history for git diff
- name: Find tutorial agents to build
id: get-agents
env:
REBUILD_ALL: ${{ inputs.rebuild_all }}
run: |
# Find all tutorial directories with manifest.yaml
all_agents=$(find examples/tutorials -name "manifest.yaml" -exec dirname {} \; | sort)
agents_to_build=()
# Output all agents for deprecation check
all_agents_json=$(printf '%s\n' $all_agents | jq -R -s -c 'split("\n") | map(select(length > 0))')
echo "all_agents=$all_agents_json" >> $GITHUB_OUTPUT
if [ "$REBUILD_ALL" = "true" ]; then
echo "Rebuild all agents requested"
agents_to_build=($(echo "$all_agents"))
echo "### πŸ”„ Rebuilding All Tutorial Agents" >> $GITHUB_STEP_SUMMARY
else
# Determine the base branch for comparison
if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE_BRANCH="origin/${{ github.base_ref }}"
echo "Comparing against PR base branch: $BASE_BRANCH"
else
# For pushes to main, compare against the first parent (pre-merge state)
BASE_BRANCH="HEAD^1"
echo "Comparing against previous commit: $BASE_BRANCH"
fi
# Check each agent directory for changes
for agent_dir in $all_agents; do
echo "Checking $agent_dir for changes..."
# Check if any files in this agent directory have changed
if git diff --name-only $BASE_BRANCH HEAD | grep -q "^$agent_dir/"; then
echo " βœ… Changes detected in $agent_dir"
agents_to_build+=("$agent_dir")
else
echo " ⏭️ No changes in $agent_dir - skipping build"
fi
done
echo "### πŸ”„ Changed Tutorial Agents" >> $GITHUB_STEP_SUMMARY
fi
# Convert array to JSON format and output summary
if [ ${#agents_to_build[@]} -eq 0 ]; then
echo "No agents to build"
echo "agents=[]" >> $GITHUB_OUTPUT
echo "has_agents=false" >> $GITHUB_OUTPUT
else
echo "Agents to build: ${#agents_to_build[@]}"
agents_json=$(printf '%s\n' "${agents_to_build[@]}" | jq -R -s -c 'split("\n") | map(select(length > 0))')
echo "agents=$agents_json" >> $GITHUB_OUTPUT
echo "has_agents=true" >> $GITHUB_OUTPUT
echo "" >> $GITHUB_STEP_SUMMARY
for agent in "${agents_to_build[@]}"; do
echo "- \`$agent\`" >> $GITHUB_STEP_SUMMARY
done
echo "" >> $GITHUB_STEP_SUMMARY
fi
build-agents:
needs: [find-agents]
if: ${{ needs.find-agents.outputs.has_agents == 'true' }}
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
matrix:
agent_path: ${{ fromJson(needs.find-agents.outputs.agents) }}
fail-fast: false
name: build-${{ matrix.agent_path }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.12"
- name: Get latest agentex-sdk version from PyPI
id: get-version
run: |
LATEST_VERSION=$(curl -s https://pypi.org/pypi/agentex-sdk/json | jq -r '.info.version')
echo "Latest agentex-sdk version: $LATEST_VERSION"
echo "AGENTEX_SDK_VERSION=$LATEST_VERSION" >> $GITHUB_ENV
pip install agentex-sdk==$LATEST_VERSION
echo "Installed agentex-sdk version $LATEST_VERSION"
- name: Generate Image name
id: image-name
run: |
# Remove examples/tutorials/ prefix and replace / with -
AGENT_NAME=$(echo "${{ matrix.agent_path }}" | sed 's|^examples/tutorials/||' | sed 's|/|-|g')
echo "AGENT_NAME=$AGENT_NAME" >> $GITHUB_ENV
echo "agent_name=$AGENT_NAME" >> $GITHUB_OUTPUT
echo "Agent name set to $AGENT_NAME"
- name: Login to GitHub Container Registry
# Only login if we're going to push (main branch or rebuild_all)
if: ${{ github.event_name == 'push' || inputs.rebuild_all }}
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Conditionally Push Agent Image
env:
REGISTRY: ghcr.io
run: |
AGENT_NAME="${{ steps.image-name.outputs.agent_name }}"
REPOSITORY_NAME="${{ github.repository }}/tutorial-agents/${AGENT_NAME}"
# Determine if we should push based on event type
if [ "${{ github.event_name }}" = "push" ] || [ "${{ inputs.rebuild_all }}" = "true" ]; then
SHOULD_PUSH=true
VERSION_TAG="latest"
echo "πŸš€ Building and pushing agent: ${{ matrix.agent_path }}"
else
SHOULD_PUSH=false
VERSION_TAG="${{ github.sha }}"
echo "πŸ” Validating build for agent: ${{ matrix.agent_path }}"
fi
# Build command - add --push only if we should push
BUILD_ARGS="--manifest ${{ matrix.agent_path }}/manifest.yaml --registry ${REGISTRY} --tag ${VERSION_TAG} --platforms linux/amd64,linux/arm64 --repository-name ${REPOSITORY_NAME}"
if [ "$SHOULD_PUSH" = "true" ]; then
agentex agents build $BUILD_ARGS --push
echo "βœ… Successfully built and pushed: ${REGISTRY}/${REPOSITORY_NAME}:${VERSION_TAG}"
# Set full image name for validation step
echo "FULL_IMAGE=${REGISTRY}/${REPOSITORY_NAME}:${VERSION_TAG}" >> $GITHUB_ENV
else
agentex agents build $BUILD_ARGS
echo "βœ… Build validation successful for: ${{ matrix.agent_path }}"
# Set full image name for validation step (local build)
echo "FULL_IMAGE=${REGISTRY}/${REPOSITORY_NAME}:${VERSION_TAG}" >> $GITHUB_ENV
fi
- name: Validate agent image
run: |
set -e
FULL_IMAGE="${{ env.FULL_IMAGE }}"
AGENT_PATH="${{ matrix.agent_path }}"
AGENT_NAME="${{ env.AGENT_NAME }}"
echo "πŸ” Validating agent image: $FULL_IMAGE"
# Determine ACP type from path
if [[ "$AGENT_PATH" == *"10_async"* ]]; then
ACP_TYPE="async"
else
ACP_TYPE="sync"
fi
# Common environment variables for validation
ENV_VARS="-e ENVIRONMENT=development \
-e AGENT_NAME=${AGENT_NAME} \
-e ACP_URL=http://localhost:8000 \
-e ACP_PORT=8000 \
-e ACP_TYPE=${ACP_TYPE}"
# 1. Validate ACP entry point exists and is importable
echo "πŸ“¦ Checking ACP entry point..."
docker run --rm $ENV_VARS "$FULL_IMAGE" python -c "from project.acp import acp; print('βœ… ACP entry point validated')"
# 2. Check if tests/test_agent.py exists (required for integration tests)
# Tests are located at /app/<agent_dir>/tests/test_agent.py
echo "πŸ§ͺ Checking for tests/test_agent.py..."
TEST_FILE=$(docker run --rm "$FULL_IMAGE" find /app -name "test_agent.py" -path "*/tests/*" 2>/dev/null | head -1)
if [ -n "$TEST_FILE" ]; then
echo "βœ… Found test file at: $TEST_FILE"
else
echo "❌ No tests/test_agent.py found in image - this is required for all tutorial agents"
echo " Please add a tests/test_agent.py file to your agent"
exit 1
fi
# 3. Validate container can start (may fail due to missing services, but should initialize)
echo "πŸ₯ Validating container starts..."
CONTAINER_NAME="validate-agent-$$"
# Start container in background with required env vars
docker run -d --name "$CONTAINER_NAME" \
$ENV_VARS \
-p 8000:8000 \
"$FULL_IMAGE"
# Give it a few seconds to attempt startup
sleep 5
# Check if container is still running (it may exit due to missing services, that's ok)
# We just want to see that it attempted to start properly
echo "πŸ“‹ Container logs:"
docker logs "$CONTAINER_NAME" 2>&1 || true
# Check for successful ACP initialization in logs
if docker logs "$CONTAINER_NAME" 2>&1 | grep -q "instance created "; then
echo "βœ… Container initialized ACP successfully"
else
echo "⚠️ Could not verify ACP initialization from logs"
fi
# Cleanup container
docker stop "$CONTAINER_NAME" > /dev/null 2>&1 || true
docker rm "$CONTAINER_NAME" > /dev/null 2>&1 || true
echo "βœ… All validations passed for: $FULL_IMAGE"
deprecate-agents:
name: "Deprecate Removed Agents"
runs-on: ubuntu-latest
needs: [find-agents]
steps:
- name: Find and delete deprecated agent packages
env:
GITHUB_TOKEN: ${{ secrets.PACKAGE_TOKEN }}
run: |
set -e
echo "πŸ” Agents in repo (from find-agents):"
# Convert JSON array of paths to package names
# e.g., "examples/tutorials/00_sync/000_hello_acp" -> "00_sync-000_hello_acp"
REPO_AGENTS=$(echo '${{ needs.find-agents.outputs.all_agents }}' | jq -r '.[]' | \
sed 's|examples/tutorials/||' | \
sed 's|/|-|g')
echo "$REPO_AGENTS"
echo ""
echo "πŸ” Fetching packages from GitHub Container Registry..."
PACKAGES=$(curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/orgs/scaleapi/packages?package_type=container&per_page=100")
# Check for API errors
if echo "$PACKAGES" | jq -e '.message' > /dev/null 2>&1; then
echo "❌ GitHub API error:"
echo "$PACKAGES" | jq '.'
exit 1
fi
# Filter for tutorial-agents from this repo
TUTORIAL_PACKAGES=$(echo "$PACKAGES" | \
jq -r '.[] | select(.repository != null and .repository.name == "scale-agentex-python" and (.name | contains("tutorial-agents"))) | .name')
echo "Tutorial packages in registry:"
echo "$TUTORIAL_PACKAGES"
echo ""
echo "πŸ” Checking for deprecated packages..."
while IFS= read -r package_name; do
[ -z "$package_name" ] && continue
# Extract agent name: scale-agentex-python/tutorial-agents/00_sync-000_hello_acp -> 00_sync-000_hello_acp
agent_name=$(echo "$package_name" | sed 's|.*/tutorial-agents/||')
if ! echo "$REPO_AGENTS" | grep -q "^${agent_name}$"; then
echo "πŸ—‘οΈ $agent_name - NOT in repo, deleting..."
# URL encode the package name (replace / with %2F)
encoded_package=$(echo "$package_name" | sed 's|/|%2F|g')
response=$(curl -s -w "\n%{http_code}" -X DELETE \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/orgs/scaleapi/packages/container/${encoded_package}")
http_code=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
if [ "$http_code" = "204" ] || [ "$http_code" = "200" ]; then
echo " βœ… Deleted: $package_name"
else
echo " ⚠️ Failed to delete $package_name (HTTP $http_code): $body"
fi
fi
done <<< "$TUTORIAL_PACKAGES"
echo ""
echo "βœ… Deprecation check complete"