summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkirillrdy <kirillrdy@gmail.com>2026-01-18 20:14:42 +0000
committerGitHub <noreply@github.com>2026-01-18 20:14:42 +0000
commit846c5bb77e65681d3097b2ba38e74597327a3a26 (patch)
tree4fabe8ac42c6f43db82017f4ffb474f47b627c61
parentb38848f4e234f6270ad0e386483cb5f81240656f (diff)
parent02527735ac771bd40ba51f4993dd16514800e276 (diff)
exo: 0.0.14-alpha -> 1.0.62 (#477442)
-rw-r--r--pkgs/by-name/ex/exo/inject-dashboard-path.patch17
-rw-r--r--pkgs/by-name/ex/exo/make-encoding-init-lazy.patch29
-rw-r--r--pkgs/by-name/ex/exo/package.nix213
3 files changed, 213 insertions, 46 deletions
diff --git a/pkgs/by-name/ex/exo/inject-dashboard-path.patch b/pkgs/by-name/ex/exo/inject-dashboard-path.patch
new file mode 100644
index 000000000000..29f964446dbb
--- /dev/null
+++ b/pkgs/by-name/ex/exo/inject-dashboard-path.patch
@@ -0,0 +1,17 @@
+diff --git a/src/exo/utils/dashboard_path.py b/src/exo/utils/dashboard_path.py
+index b5ce9c04..ec60ef4a 100644
+--- a/src/exo/utils/dashboard_path.py
++++ b/src/exo/utils/dashboard_path.py
+@@ -5,11 +5,7 @@ from typing import cast
+
+
+ def find_dashboard() -> Path:
+- dashboard = (
+- _find_dashboard_in_env()
+- or _find_dashboard_in_repo()
+- or _find_dashboard_in_bundle()
+- )
++ dashboard = "@dashboard@"
+ if not dashboard:
+ raise FileNotFoundError(
+ "Unable to locate dashboard assets - make sure the dashboard has been built, or export DASHBOARD_DIR if you've built the dashboard elsewhere."
diff --git a/pkgs/by-name/ex/exo/make-encoding-init-lazy.patch b/pkgs/by-name/ex/exo/make-encoding-init-lazy.patch
new file mode 100644
index 000000000000..7a51317853ca
--- /dev/null
+++ b/pkgs/by-name/ex/exo/make-encoding-init-lazy.patch
@@ -0,0 +1,29 @@
+diff --git a/src/exo/master/api.py b/src/exo/master/api.py
+index 30f87e2a..4aac51fe 100644
+--- a/src/exo/master/api.py
++++ b/src/exo/master/api.py
+@@ -67,7 +67,14 @@ from exo.utils.channels import Receiver, Sender, channel
+ from exo.utils.dashboard_path import find_dashboard
+ from exo.utils.event_buffer import OrderedBuffer
+
+-encoding = load_harmony_encoding(HarmonyEncodingName.HARMONY_GPT_OSS)
++_encoding: object = None
++
++
++def get_encoding():
++ global _encoding
++ if _encoding is None:
++ _encoding = load_harmony_encoding(HarmonyEncodingName.HARMONY_GPT_OSS)
++ return _encoding
+
+
+ def chunk_to_response(
+@@ -382,7 +389,7 @@ class API:
+ )
+
+ async def _process_gpt_oss(self, token_chunks: Receiver[TokenChunk]):
+- stream = StreamableParser(encoding, role=Role.ASSISTANT)
++ stream = StreamableParser(_encoding, role=Role.ASSISTANT)
+ thinking = False
+
+ async for chunk in token_chunks:
diff --git a/pkgs/by-name/ex/exo/package.nix b/pkgs/by-name/ex/exo/package.nix
index 919815c9ec61..fae8710e96ad 100644
--- a/pkgs/by-name/ex/exo/package.nix
+++ b/pkgs/by-name/ex/exo/package.nix
@@ -3,80 +3,201 @@
stdenv,
fetchFromGitHub,
python3Packages,
- gitUpdater,
-}:
-python3Packages.buildPythonApplication rec {
- pname = "exo";
- version = "0.0.14-alpha";
- pyproject = true;
+ replaceVars,
+ macmon,
+
+ # pyo3-bindings
+ rustPlatform,
+
+ # dashboard
+ buildNpmPackage,
+ fetchNpmDeps,
+ writableTmpDirAsHomeHook,
+
+ nix-update-script,
+}:
+let
+ version = "1.0.63";
src = fetchFromGitHub {
+ name = "exo";
owner = "exo-explore";
repo = "exo";
tag = "v${version}";
- hash = "sha256-GoYfpr6oFpreWQtSomOwgfzSoBAbjqGZ1mcc0u9TBl4=";
+ hash = "sha256-aQ3rGLtT/zvIVdKQcwqODulzEHBKg7KMkBg3KJEscho=";
};
- build-system = with python3Packages; [ setuptools ];
+ pyo3-bindings = python3Packages.buildPythonPackage (finalAttrs: {
+ pname = "exo-pyo3-bindings";
+ inherit version src;
+ pyproject = true;
+
+ buildAndTestSubdir = "rust/exo_pyo3_bindings";
+
+ cargoDeps = rustPlatform.fetchCargoVendor {
+ inherit (finalAttrs) pname src version;
+ hash = "sha256-N7B1WFqPdqeNPZe9hXGyX7F3EbB1spzeKc19BFDDwls=";
+ };
+
+ # Bypass rust nightly features not being available on rust stable
+ env.RUSTC_BOOTSTRAP = 1;
+
+ nativeBuildInputs = [
+ rustPlatform.cargoSetupHook
+ rustPlatform.maturinBuildHook
+ ];
+
+ nativeCheckInputs = with python3Packages; [
+ pytest-asyncio
+ pytestCheckHook
+ ];
+
+ enabledTestPaths = [
+ "rust/exo_pyo3_bindings/tests/"
+ ];
+ });
+
+ dashboard = buildNpmPackage (finalAttrs: {
+ pname = "exo-dashboard";
+ inherit src version;
+
+ sourceRoot = "${finalAttrs.src.name}/dashboard";
+
+ npmDeps = fetchNpmDeps {
+ inherit (finalAttrs)
+ pname
+ version
+ src
+ sourceRoot
+ ;
+ fetcherVersion = 2;
+ hash = "sha256-w3FZL/yy8R+SWCQF7+v21sKyizvZMmipG6IfhJeSjyQ=";
+ };
+ });
+in
+python3Packages.buildPythonApplication (finalAttrs: {
+ pname = "exo";
+ pyproject = true;
+
+ inherit version src;
+
+ patches = [
+ (replaceVars ./inject-dashboard-path.patch {
+ dashboard = "${dashboard}/lib/node_modules/${dashboard.pname}/build";
+ })
+ ];
+
+ postPatch = ''
+ substituteInPlace pyproject.toml \
+ --replace-fail "uv_build>=0.8.9,<0.9.0" "uv_build"
+ ''
+ # MemoryObjectStreamState was renamed in
+ # https://github.com/agronholm/anyio/pull/1009/changes/bdc945a826d0d5917aea3517ceb9fe335b468094
+ + ''
+ substituteInPlace src/exo/utils/channels.py \
+ --replace-fail \
+ "MemoryObjectStreamState as AnyioState," \
+ "_MemoryObjectStreamState as AnyioState,"
+ ''
+ + lib.optionalString stdenv.hostPlatform.isDarwin ''
+ substituteInPlace src/exo/worker/utils/macmon.py \
+ --replace-fail \
+ 'path = shutil.which("macmon")' \
+ 'path = "${lib.getExe macmon}"'
+
+ substituteInPlace src/exo/worker/utils/tests/test_macmon.py \
+ --replace-fail \
+ 'cmd=["macmon"' \
+ 'cmd=["${lib.getExe macmon}"'
+ '';
+
+ build-system = with python3Packages; [
+ uv-build
+ ];
pythonRelaxDeps = true;
- pythonRemoveDeps = [ "uuid" ];
-
- dependencies = with python3Packages; [
- aiohttp
- aiohttp-cors
- aiofiles
- grpcio
- grpcio-tools
- jinja2
- numpy
- nuitka
- nvidia-ml-py
- opencv-python
- pillow
- prometheus-client
- protobuf
- psutil
- pydantic
- requests
- rich
- scapy
- tqdm
- transformers
- tinygrad
- uvloop
+ pythonRemoveDeps = [
+ "types-aiofiles"
+ "uuid"
];
+ dependencies =
+ with python3Packages;
+ [
+ aiofiles
+ aiohttp
+ aiohttp-cors
+ anyio
+ fastapi
+ filelock
+ grpcio
+ grpcio-tools
+ httpx
+ huggingface-hub
+ hypercorn
+ jinja2
+ loguru
+ mlx
+ mlx-lm
+ nvidia-ml-py
+ openai
+ openai-harmony
+ opencv-python
+ pillow
+ prometheus-client
+ psutil
+ pydantic
+ pyo3-bindings
+ rustworkx
+ scapy
+ tiktoken
+ tinygrad
+ transformers
+ uvloop
+ ]
+ ++ sqlalchemy.optional-dependencies.asyncio;
pythonImportsCheck = [
"exo"
- "exo.inference.tinygrad.models"
+ "exo.main"
];
- nativeCheckInputs = with python3Packages; [
- mlx
- pytestCheckHook
+ nativeCheckInputs = [
+ python3Packages.pytest-asyncio
+ python3Packages.pytestCheckHook
+ writableTmpDirAsHomeHook
];
- disabledTestPaths = [
- "test/test_tokenizers.py"
+ # Otherwise fails with 'import file mismatch'
+ preCheck = ''
+ rm src/exo/__init__.py
+ '';
+
+ disabledTests = lib.optionals stdenv.hostPlatform.isDarwin [
+ # AssertionError: assert "MacMon not found in PATH" in str(exc_info.value)
+ "test_macmon_not_found_raises_macmon_error"
+
+ # ValueError: zip() argument 2 is longer than argument 1
+ "test_events_processed_in_correct_order"
];
- # Tests require `mlx` which is not supported on linux.
- doCheck = stdenv.hostPlatform.isDarwin;
+ disabledTestPaths = [
+ # All tests hang indefinitely
+ "src/exo/worker/tests/unittests/test_mlx/test_tokenizers.py"
+ ];
passthru = {
- updateScript = gitUpdater {
- rev-prefix = "v-";
- };
+ updateScript = nix-update-script { };
+ exo-pyo3-bindings = pyo3-bindings;
+ exo-dashboard = dashboard;
};
meta = {
description = "Run your own AI cluster at home with everyday devices";
homepage = "https://github.com/exo-explore/exo";
- changelog = "https://github.com/exo-explore/exo/releases/tag/v${version}";
+ changelog = "https://github.com/exo-explore/exo/releases/tag/${finalAttrs.src.tag}";
license = lib.licenses.gpl3Only;
maintainers = with lib.maintainers; [ GaetanLepage ];
mainProgram = "exo";
};
-}
+})