Test message re-ordering fixes #518
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" |