[object Object]

← back to Exo

feat: /state/paths (#1796)

c6c5a3e73c23cef93eacb33591ea7ab7fe1c1c21 · 2026-03-30 11:10:00 +0100 · Evan Quiney

adds a path option to the /state endpoint, allowing you to query
subfields of state without grabbing the whole blob

## test plan
poking around in the api

Files touched

Diff

commit c6c5a3e73c23cef93eacb33591ea7ab7fe1c1c21
Author: Evan Quiney <evanev7@gmail.com>
Date:   Mon Mar 30 11:10:00 2026 +0100

    feat: /state/paths (#1796)
    
    adds a path option to the /state endpoint, allowing you to query
    subfields of state without grabbing the whole blob
    
    ## test plan
    poking around in the api
---
 src/exo/api/main.py | 23 +++++++++++++++++++++--
 1 file changed, 21 insertions(+), 2 deletions(-)

diff --git a/src/exo/api/main.py b/src/exo/api/main.py
index 7862801f..b09e8504 100644
--- a/src/exo/api/main.py
+++ b/src/exo/api/main.py
@@ -7,7 +7,7 @@ from collections.abc import AsyncGenerator, Awaitable, Callable, Iterable
 from datetime import datetime, timezone
 from http import HTTPStatus
 from pathlib import Path
-from typing import Annotated, Literal, cast
+from typing import Annotated, Any, Literal, cast
 from uuid import uuid4
 
 import anyio
@@ -342,7 +342,8 @@ class API:
         self.app.get("/ollama/api/ps")(self.ollama_ps)
         self.app.get("/ollama/api/version")(self.ollama_version)
 
-        self.app.get("/state")(lambda: self.state)
+        self.app.get("/state")(self.get_state)
+        self.app.get("/state/{path:path}")(self.get_state)
         self.app.get("/events")(self.stream_events)
         self.app.post("/download/start")(self.start_download)
         self.app.delete("/download/{node_id}/{model_id:path}")(self.delete_download)
@@ -354,6 +355,24 @@ class API:
         self.app.get("/onboarding")(self.get_onboarding)
         self.app.post("/onboarding")(self.complete_onboarding)
 
+    def get_state(self, path: str = ""):
+        if path == "":
+            return self.state
+        try:
+            x = self.state.model_dump(by_alias=True)
+            for attr in path.split("/"):
+                if attr != "":
+                    if isinstance(x, dict):
+                        x = x[attr]  # pyright: ignore[reportUnknownVariableType]
+                    elif isinstance(x, list):
+                        x = x[int(attr)]  # pyright: ignore[reportUnknownVariableType]
+            return cast(Any, x)  # pyright: ignore[reportAny]
+        except Exception as e:
+            raise HTTPException(
+                status_code=404,
+                detail=f"unable to find path '{path.replace('/', '.')}' in state json",
+            ) from e
+
     async def place_instance(self, payload: PlaceInstanceParams):
         command = PlaceInstance(
             model_card=await ModelCard.load(payload.model_id),

← 10ef7ec9 feat: add Firefox AI sidebar (?q=) support to dashboard (#18  ·  back to Exo  ·  Improve exo harness with path state (#1815) 2efbb8ab →