feat(platform): Add support for APFEL as part of the dependencies and models for the Cookbook. (#2657)

* feat(platform): add support for Apple Silicon detection in platform compatibility

test(tests): enhance shell_routes tests for Apple Silicon compatibility

* fix issues with missing import

* fix: correct package name in package-lock.json and enhance package installation commands in shell_routes.py and cookbook.js

* feat: add Apfel startup and health checks on macOS

- bootstrap Apfel via Homebrew on arm64 macOS
- start `apfel --serve --port 11435` detached for Odysseus
- verify readiness via `/health`
- clean up the Apfel process on exit or Ctrl+C

* fix: duplicate variable declaration post-merge conflict
- Should fix `node` CI issues.

* fix: issues with the update status of the APFEL dependency.
- fixed by changing the main conditional that determines the update.

* Fix: Remove unnecessary whitespaces and formatting for the model_routes.py file.

* Fix: whitespace issues with the model_routes file

* Fix: Remove unnecessary whitespaces and formatting for the model_routes.py file. Final

* Fix: Fixed updates using PIP for APFEL instead of custom cmd
This commit is contained in:
Sebastian Andres El Khoury Seoane
2026-06-07 16:28:02 +01:00
committed by GitHub
parent 8f2c8d2dc8
commit 8d9d4ec9c6
8 changed files with 684 additions and 275 deletions
+34 -18
View File
@@ -44,8 +44,7 @@ def discover_tailscale_hosts() -> List[str]:
hosts = []
try:
result = subprocess.run(
["tailscale", "status", "--json"],
capture_output=True, text=True, timeout=5
["tailscale", "status", "--json"], capture_output=True, text=True, timeout=5
)
if result.returncode != 0:
return hosts
@@ -154,9 +153,13 @@ class ModelDiscovery:
r = httpx.get(f"http://{host}:{port}/api/v1/models", timeout=1.5)
if r.is_success:
models = (r.json() or {}).get("models")
if (isinstance(models, list) and models
and isinstance(models[0], dict)
and "key" in models[0] and "architecture" in models[0]):
if (
isinstance(models, list)
and models
and isinstance(models[0], dict)
and "key" in models[0]
and "architecture" in models[0]
):
return "lmstudio"
except Exception:
pass
@@ -192,12 +195,15 @@ class ModelDiscovery:
logger.info(f"Scanning {len(hosts)} hosts for models: {hosts}")
# Well-known ports: 8000-8020 (vLLM, llama.cpp, SGLang, Cookbook),
# 1234 (LM Studio), 11434 (Ollama)
ports = list(range(8000, 8021)) + [1234, 11434]
# 1234 (LM Studio), 11434 (Ollama), 11435 for APFEL as its default port is
# occupied by Ollama. The env vars can add more ports which will be merged in.
ports = list(range(8000, 8021)) + [1234, 11434, 11435]
ports += [p for p in sorted(self._extra_ports) if p not in ports]
targets = [(h, p) for h in hosts for p in ports]
seen_models = set() # dedupe by (port, model_ids) to avoid same machine via different IPs
seen_models = (
set()
) # dedupe by (port, model_ids) to avoid same machine via different IPs
with ThreadPoolExecutor(max_workers=50) as pool:
futures = {pool.submit(self._check_port, h, p): (h, p) for h, p in targets}
@@ -212,7 +218,9 @@ class ModelDiscovery:
# Sort by host then port for consistent ordering
items.sort(key=lambda x: (x["host"], x["port"]))
logger.info(f"Discovered {len(items)} model endpoints across {len(hosts)} hosts")
logger.info(
f"Discovered {len(items)} model endpoints across {len(hosts)} hosts"
)
return {"hosts": hosts, "items": items}
def get_providers(self) -> Dict[str, Any]:
@@ -223,15 +231,23 @@ class ModelDiscovery:
if self.openai_api_key:
openai_models = [
"gpt-5.2-codex", "gpt-4o-mini", "gpt-image-1.5",
"gpt-4o", "gpt-5.2", "gpt-5.2-pro",
"gpt-5.2-codex",
"gpt-4o-mini",
"gpt-image-1.5",
"gpt-4o",
"gpt-5.2",
"gpt-5.2-pro",
]
providers.append({
"provider": "openai",
"items": [{
"url": "https://api.openai.com/v1/chat/completions",
"models": openai_models
}]
})
providers.append(
{
"provider": "openai",
"items": [
{
"url": "https://api.openai.com/v1/chat/completions",
"models": openai_models,
}
],
}
)
return {"providers": providers}