#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" TMP_DIR=$(mktemp -d) EDGE_PID="" NODE_PID="" OLLAMA_PID="" EDGE_FD_OPEN=0 cleanup() { if [ "$EDGE_FD_OPEN" -eq 1 ]; then { echo "/exit" >&3; } 2>/dev/null || true exec 3>&- 2>/dev/null || true fi if [ -n "$EDGE_PID" ]; then wait "$EDGE_PID" 2>/dev/null || true; fi if [ -n "$NODE_PID" ]; then kill "$NODE_PID" 2>/dev/null || true; fi if [ -n "$OLLAMA_PID" ]; then kill "$OLLAMA_PID" 2>/dev/null || true; fi rm -rf "$TMP_DIR" 2>/dev/null || true } trap cleanup EXIT PORT=$((31000 + RANDOM % 5000)) BOOTSTRAP_PORT=$((21000 + RANDOM % 5000)) OPENAI_PORT=$((36000 + RANDOM % 5000)) OLLAMA_PORT=$((41000 + RANDOM % 5000)) EDGE_METRICS_PORT=$((46000 + RANDOM % 5000)) NODE_METRICS_PORT=$((51000 + RANDOM % 5000)) MODEL="fake-ollama-model" FAKE_OLLAMA="$TMP_DIR/fake_ollama.go" cat > "$FAKE_OLLAMA" <<'EOF' package main import ( "encoding/json" "log" "net/http" "os" ) type chatRequest struct { Model string `json:"model"` Messages []struct { Role string `json:"role"` Content string `json:"content"` } `json:"messages"` Options struct { NumCtx int `json:"num_ctx"` } `json:"options"` } func main() { if len(os.Args) < 2 { log.Fatal("missing listen addr") } mux := http.NewServeMux() mux.HandleFunc("/api/tags", func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"models":[{"name":"fake-ollama-model"}]}`)) }) mux.HandleFunc("/api/chat", func(w http.ResponseWriter, r *http.Request) { var req chatRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if req.Model != "fake-ollama-model" { http.Error(w, "unexpected model: "+req.Model, http.StatusBadRequest) return } if req.Options.NumCtx != 262144 { http.Error(w, "unexpected num_ctx", http.StatusBadRequest) return } w.Header().Set("Content-Type", "application/x-ndjson") _, _ = w.Write([]byte(`{"message":{"role":"assistant","thinking":"thinking..."},"done":false}` + "\n")) if f, ok := w.(http.Flusher); ok { f.Flush() } _, _ = w.Write([]byte(`{"message":{"role":"assistant","content":"IOP_OPENAI_"},"done":false}` + "\n")) if f, ok := w.(http.Flusher); ok { f.Flush() } _, _ = w.Write([]byte(`{"message":{"role":"assistant","content":"OLLAMA_OK"},"done":false}` + "\n")) _, _ = w.Write([]byte(`{"done":true,"prompt_eval_count":5,"eval_count":2}` + "\n")) }) log.Fatal(http.ListenAndServe(os.Args[1], mux)) } EOF go run "$FAKE_OLLAMA" "127.0.0.1:$OLLAMA_PORT" > "$TMP_DIR/fake_ollama.out" 2>&1 & OLLAMA_PID=$! wait_port() { local host="$1" local port="$2" local label="$3" local deadline=$((SECONDS + 20)) while ! timeout 1 bash -c 'cat < /dev/null > /dev/tcp/"$1"/"$2"' _ "$host" "$port" 2>/dev/null; do if (( SECONDS >= deadline )); then echo "[openai-ollama] $label did not open on $host:$port" return 1 fi sleep 0.2 done } wait_port 127.0.0.1 "$OLLAMA_PORT" "fake ollama" EDGE_CONFIG="$TMP_DIR/edge.yaml" NODE_CONFIG="$TMP_DIR/node.yaml" cat > "$EDGE_CONFIG" < "$NODE_CONFIG" < "$EDGE_OUT" 2>&1 & EDGE_PID=$! exec 3> "$TMP_DIR/edge_fifo" EDGE_FD_OPEN=1 wait_port 127.0.0.1 "$PORT" "edge node transport" wait_port 127.0.0.1 "$OPENAI_PORT" "edge openai api" IOP_NODE_CONFIG="$NODE_CONFIG" "$REPO_ROOT/scripts/dev/node.sh" > "$NODE_OUT" 2>&1 & NODE_PID=$! deadline=$((SECONDS + 30)) while ! grep -q '\[node0-evt\] connected reason="registered"' "$EDGE_OUT"; do if (( SECONDS >= deadline )); then echo "[openai-ollama] node registration timed out" echo "=== EDGE OUTPUT ==="; cat "$EDGE_OUT" echo "=== NODE OUTPUT ==="; cat "$NODE_OUT" exit 1 fi sleep 0.2 done MODELS_OUT="$TMP_DIR/models.json" curl -fsS "http://127.0.0.1:$OPENAI_PORT/v1/models" > "$MODELS_OUT" grep -q "$MODEL" "$MODELS_OUT" CHAT_OUT="$TMP_DIR/chat.json" curl -fsS \ -H "Content-Type: application/json" \ -d '{"model":"client-request-model","messages":[{"role":"user","content":"say the test token"}]}' \ "http://127.0.0.1:$OPENAI_PORT/v1/chat/completions" > "$CHAT_OUT" grep -q "IOP_OPENAI_OLLAMA_OK" "$CHAT_OUT" grep -q "reasoning_content" "$CHAT_OUT" RESPONSES_OUT="$TMP_DIR/responses.json" curl -fsS \ -H "Content-Type: application/json" \ -d '{"model":"client-request-model","input":"say the responses test token","stream":false,"metadata":{"request_id":"e2e-openai-ollama","task_id":"task-smoke","source":"manual","inference":{"target":"fake-ollama-model"}}}' \ "http://127.0.0.1:$OPENAI_PORT/v1/responses" > "$RESPONSES_OUT" grep -q "IOP_OPENAI_OLLAMA_OK" "$RESPONSES_OUT" grep -q '"output_text"' "$RESPONSES_OUT" STREAM_OUT="$TMP_DIR/stream.txt" curl -fsS -N \ -H "Content-Type: application/json" \ -d '{"model":"client-request-model","stream":true,"messages":[{"role":"user","content":"stream the test token"}]}' \ "http://127.0.0.1:$OPENAI_PORT/v1/chat/completions" > "$STREAM_OUT" grep -q '"reasoning_content":"thinking..."' "$STREAM_OUT" grep -q '"content":"IOP_OPENAI_"' "$STREAM_OUT" grep -q 'data: \[DONE\]' "$STREAM_OUT" SMOKE_OUT="$TMP_DIR/iop-edge-smoke-openai.txt" (cd "$REPO_ROOT" && go run ./apps/edge/cmd/edge smoke openai \ --model "$MODEL" \ --base-url "http://127.0.0.1:$OPENAI_PORT" \ --prompt "say the command smoke token" \ --timeout 20s) > "$SMOKE_OUT" grep -q "Step 3: Checking /v1/responses ... \[OK\]" "$SMOKE_OUT" grep -q "IOP Edge OpenAI Smoke Test SUCCESS!" "$SMOKE_OUT" if grep -i -E "node reported error|error run_id=|\[[^]]+-evt\] error|panic:" "$EDGE_OUT" "$NODE_OUT" >/dev/null; then echo "[openai-ollama] detected failure marker" echo "=== EDGE OUTPUT ==="; cat "$EDGE_OUT" echo "=== NODE OUTPUT ==="; cat "$NODE_OUT" exit 1 fi echo "[openai-ollama] OpenAI-compatible Ollama serving test PASSED."