Skip to content

feat(sdk): gate switch llm default tool #23

feat(sdk): gate switch llm default tool

feat(sdk): gate switch llm default tool #23

---
name: Publish agent-server release artifacts
# On release published or push to main:
# 1. Build the agent-server PyInstaller binary on Linux + macOS for both
# x86_64 and arm64, smoke-test it, and upload workflow artifacts.
# 2. On release events/manual runs, attach those binaries plus a combined
# SHA256SUMS file to the GitHub release.
# 3. Smoke-test the multi-arch Docker images pushed by `server.yml`,
# verifying that every published variant has a manifest covering both
# linux/amd64 and linux/arm64 and that the container actually boots
# and answers /health on each architecture.
on:
push:
branches: [main]
release:
types: [published]
workflow_dispatch:
inputs:
release_tag:
description: Existing release tag (e.g. v1.20.1)
required: true
type: string
permissions:
contents: write
packages: read
jobs:
resolve-tag:
name: Resolve artifact and image tag
runs-on: ubuntu-24.04
outputs:
tag: ${{ steps.resolve.outputs.tag }}
version: ${{ steps.resolve.outputs.version }}
image_tag: ${{ steps.resolve.outputs.image_tag }}
steps:
- id: resolve
shell: bash
run: |
set -euo pipefail
if [[ "${{ github.event_name }}" == "release" ]]; then
TAG="${{ github.event.release.tag_name }}"
VERSION="${TAG#v}"
elif [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
TAG="${{ inputs.release_tag }}"
VERSION="${TAG#v}"
elif [[ "${{ github.event_name }}" == "push" ]]; then
TAG=""
VERSION="${GITHUB_SHA::7}"
else
echo "ERROR: unsupported event '${{ github.event_name }}'"
exit 1
fi
if [[ -n "$TAG" ]] && ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+([a-zA-Z0-9.+-]*)?$ ]]; then
echo "ERROR: unexpected version '$VERSION' (from tag '$TAG')"
exit 1
fi
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "image_tag=$VERSION" >> "$GITHUB_OUTPUT"
echo "📦 Tag: ${TAG:-<push>} Image tag: $VERSION"
build-binary:
name: Build (${{ matrix.os_label }}-${{ matrix.arch }})
needs: resolve-tag
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- runner: ubuntu-24.04
os_label: linux
arch: x86_64
- runner: ubuntu-24.04-arm
os_label: linux
arch: arm64
- runner: macos-13
os_label: macos
arch: x86_64
- runner: macos-14
os_label: macos
arch: arm64
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: latest
python-version: '3.13'
- name: Install dependencies
run: uv sync --dev
- name: Build binary
run: make build-server
- name: Smoke-test binary
shell: bash
run: |
set -euo pipefail
./dist/openhands-agent-server --help
echo "Testing server startup and template loading..."
./dist/openhands-agent-server --port 8002 > server_test.log 2>&1 &
SERVER_PID=$!
cleanup() {
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
if [ -f server_test.log ]; then
echo "----- server_test.log (tail) -----"
tail -100 server_test.log || true
rm -f server_test.log
fi
}
trap cleanup EXIT
# Poll /health for up to 90s; fail if it never comes up.
for i in $(seq 1 30); do
if grep -q "system_prompt.j2.*not found" server_test.log 2>/dev/null; then
echo "ERROR: Template files not found in binary!"
exit 1
fi
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
echo "ERROR: Server process exited before /health responded"
exit 1
fi
if curl -f -s http://localhost:8002/health >/dev/null 2>&1; then
echo "✓ /health responded after ${i} attempt(s)"
echo "✓ Binary smoke test passed"
exit 0
fi
sleep 3
done
echo "ERROR: /health never responded within 90s"
exit 1
- name: Stage release asset
shell: bash
env:
ASSET: agent-server-${{ needs.resolve-tag.outputs.version }}-${{ matrix.os_label }}-${{ matrix.arch }}
run: |
set -euo pipefail
mkdir -p release-assets
cp dist/openhands-agent-server "release-assets/${ASSET}"
ls -la release-assets/
- name: Upload binary as workflow artifact
uses: actions/upload-artifact@v7
with:
name: binary-${{ matrix.os_label }}-${{ matrix.arch }}
path: release-assets/agent-server-*
retention-days: 7
if-no-files-found: error
publish-binaries:
name: Publish binaries + SHA256SUMS
needs: [resolve-tag, build-binary]
if: github.event_name != 'push'
runs-on: ubuntu-24.04
steps:
- name: Download binary artifacts
uses: actions/download-artifact@v8
with:
pattern: binary-*
merge-multiple: true
path: release-assets
- name: Generate combined SHA256SUMS
shell: bash
run: |
set -euo pipefail
cd release-assets
ls -la
shasum -a 256 agent-server-* | sort > SHA256SUMS
cat SHA256SUMS
- name: Attach binaries + SHA256SUMS to release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ needs.resolve-tag.outputs.tag }}
shell: bash
run: |
set -euo pipefail
cd release-assets
gh release upload "$TAG" \
agent-server-* \
SHA256SUMS \
--clobber \
--repo "${{ github.repository }}"
docker-smoke-test:
name: Docker (${{ matrix.variant }}-${{ matrix.arch }})
needs: resolve-tag
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- variant: python
arch: amd64
runner: ubuntu-24.04
- variant: python
arch: arm64
runner: ubuntu-24.04-arm
- variant: java
arch: amd64
runner: ubuntu-24.04
- variant: java
arch: arm64
runner: ubuntu-24.04-arm
- variant: golang
arch: amd64
runner: ubuntu-24.04
- variant: golang
arch: arm64
runner: ubuntu-24.04-arm
env:
IMAGE: ghcr.io/openhands/agent-server
IMAGE_TAG: ${{ needs.resolve-tag.outputs.image_tag }}
VARIANT: ${{ matrix.variant }}
ARCH: ${{ matrix.arch }}
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to GHCR
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Wait for multi-arch manifest
shell: bash
run: |
set -euo pipefail
TAG_FQN="${IMAGE}:${IMAGE_TAG}-${VARIANT}"
DEADLINE=$(( $(date +%s) + 2700 )) # 45 minutes
while ! docker buildx imagetools inspect "$TAG_FQN" >/dev/null 2>&1; do
if [ "$(date +%s)" -ge "$DEADLINE" ]; then
echo "ERROR: timed out waiting for $TAG_FQN"
exit 1
fi
echo "Waiting for $TAG_FQN ..."
sleep 30
done
echo "✓ Manifest available: $TAG_FQN"
- name: Verify manifest covers linux/amd64 + linux/arm64
shell: bash
run: |
set -euo pipefail
TAG_FQN="${IMAGE}:${IMAGE_TAG}-${VARIANT}"
PLATFORMS=$(docker buildx imagetools inspect "$TAG_FQN" --raw \
| jq -r '.manifests[]?.platform | "\(.os)/\(.architecture)"' \
| sort -u)
echo "Platforms in $TAG_FQN:"
echo "$PLATFORMS"
for required in linux/amd64 linux/arm64; do
if ! echo "$PLATFORMS" | grep -qx "$required"; then
echo "ERROR: $required missing from $TAG_FQN manifest"
exit 1
fi
done
echo "✓ Both linux/amd64 and linux/arm64 are present"
- name: Pull and run on linux/${{ matrix.arch }}
shell: bash
run: |
set -euo pipefail
TAG_FQN="${IMAGE}:${IMAGE_TAG}-${VARIANT}"
CONTAINER="agent-server-smoke-${VARIANT}-${ARCH}"
echo "Pulling $TAG_FQN for linux/${ARCH} ..."
docker pull --platform="linux/${ARCH}" "$TAG_FQN"
echo "Starting container ..."
docker run --platform="linux/${ARCH}" -d --rm \
--name "$CONTAINER" \
-p 8000:8000 \
"$TAG_FQN"
cleanup() {
docker logs "$CONTAINER" 2>&1 | tail -100 || true
docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
}
trap cleanup EXIT
for i in $(seq 1 40); do
if curl -f -s http://localhost:8000/health >/dev/null 2>&1; then
echo "✓ /health responded for $TAG_FQN on linux/${ARCH}"
exit 0
fi
sleep 3
done
echo "ERROR: /health never responded for $TAG_FQN on linux/${ARCH}"
exit 1