#!/usr/bin/env bash # Deploy Cursor-parity AiRouter (Max plan / Coder routine / DeepSeek hard) to IFT set -euo pipefail AIR=/mnt/c/Users/alexc/IdeaProjects/eventHub/EventHubAiRouter cd "$AIR" echo "== Local unit + compile ==" python3 -m py_compile \ router/agent_hier.py router/router.py router/hierarchical.py router/orchestrator.py python3 -m unittest discover -s test/unit -q PRIMARY_PROVIDER=hybrid python3 scripts/gen-litellm-config.py echo "== Sync to eventhub-ift ==" scp \ router/agent_hier.py \ router/router.py \ router/hierarchical.py \ router/orchestrator.py \ eventhub-ift:/opt/ai-router-stack/router/ scp \ config/orchestration.yaml \ config/providers.yaml \ config/routing_rules.yaml \ eventhub-ift:/opt/ai-router-stack/config/ scp litellm_config.yaml eventhub-ift:/opt/ai-router-stack/litellm_config.yaml scp README.md AGENTS.md eventhub-ift:/opt/ai-router-stack/ 2>/dev/null || true ssh -o BatchMode=yes eventhub-ift bash -s <<'REMOTE' set -euo pipefail cd /opt/ai-router-stack set -a; source .env; set +a export PRIMARY_PROVIDER="${PRIMARY_PROVIDER:-hybrid}" sed -i 's/\r$//' \ router/agent_hier.py router/router.py router/hierarchical.py router/orchestrator.py \ config/orchestration.yaml config/providers.yaml config/routing_rules.yaml \ litellm_config.yaml || true echo "== Build gateway image ==" docker build -f router/Dockerfile -t "${ROUTER_IMAGE:-git.sabilin.com/eventhub/ai-router-gateway:ift}" . TS=$(date +%Y%m%d%H%M%S) ORCH="orchestration_config_${TS}" LITC="litellm_config_${TS}" docker config create "$ORCH" ./config/orchestration.yaml docker config create "$LITC" ./litellm_config.yaml OLD_ORCH=$(docker service inspect ai-router_router --format '{{range .Spec.TaskTemplate.ContainerSpec.Configs}}{{println .ConfigName}}{{end}}' | grep orchestration | tail -1 || true) OLD_LIT=$(docker service inspect ai-router_litellm --format '{{range .Spec.TaskTemplate.ContainerSpec.Configs}}{{println .ConfigName}}{{end}}' | grep litellm_config | grep -v entrypoint | tail -1 || true) echo "== Update ai-router_router ==" RARGS=(--image "${ROUTER_IMAGE:-git.sabilin.com/eventhub/ai-router-gateway:ift}" --force) RARGS+=(--config-add "source=${ORCH},target=/app/config/orchestration.yaml") if [ -n "${OLD_ORCH:-}" ]; then RARGS+=(--config-rm "$OLD_ORCH") fi docker service update "${RARGS[@]}" ai-router_router >/dev/null echo "== Update ai-router_litellm ==" LARGS=(--force) LARGS+=(--config-add "source=${LITC},target=/app/config.yaml") if [ -n "${OLD_LIT:-}" ]; then LARGS+=(--config-rm "$OLD_LIT") fi docker service update "${LARGS[@]}" ai-router_litellm >/dev/null echo "== Wait health ==" ok=0 for i in $(seq 1 60); do if curl -sf --max-time 5 https://ai-router.ift.calentiq.com/health >/dev/null \ && curl -sf --max-time 5 https://litellm.ift.calentiq.com/health/liveliness >/dev/null; then ok=1 break fi sleep 3 done if [ "$ok" != 1 ]; then echo "ERROR: health not ready" >&2 docker service ps ai-router_router --no-trunc | head -8 docker service ps ai-router_litellm --no-trunc | head -8 exit 1 fi RID=$(docker ps -q -f name=ai-router_router | head -1) LID=$(docker ps -q -f name=ai-router_litellm | head -1) echo "== Verify orchestration ==" docker exec "$RID" grep -E 'planner_model:|verifier_model:|agent_executor_model:|executor_synthetic_mode:|worker_map:|hard:' /app/config/orchestration.yaml echo "== Verify litellm novita-planner ==" docker exec "$LID" sh -c 'grep -A6 "model_name: novita-planner" /app/config.yaml | head -8' echo "== Smoke classify + chat ==" curl -sf --max-time 30 -H "Authorization: Bearer ${ROUTER_API_KEY}" \ -H "Content-Type: application/json" \ -d '{"text":"поправь timeout в router.py"}' \ https://ai-router.ift.calentiq.com/classify | head -c 400 echo curl -sf --max-time 90 -H "Authorization: Bearer ${ROUTER_API_KEY}" \ -H "Content-Type: application/json" \ -d '{"model":"smart-router","messages":[{"role":"user","content":"ping"}],"max_tokens":8}' \ https://ai-router.ift.calentiq.com/v1/chat/completions | head -c 500 echo echo CURSOR_PARITY_DEPLOY_OK REMOTE