Files
codex_truenas_helper/llamaCpp.Wrapper.app/agents_parser.py
Rushabh Gosar 5d1a0ee72b Initial commit
2026-01-07 16:54:39 -08:00

120 lines
4.3 KiB
Python

import json
import re
from dataclasses import dataclass, asdict
from pathlib import Path
from typing import List, Optional
APP_HEADER_RE = re.compile(r"^### App: (?P<name>.+?)\s*$")
IMAGE_RE = re.compile(r"image=(?P<image>[^\s]+)")
PORT_MAP_RE = re.compile(r"- tcp (?P<container>\d+) -> (?P<host>\d+|0\.0\.0\.0:(?P<host_ip_port>\d+))")
PORT_LINE_RE = re.compile(r"- tcp (?P<container>\d+) -> (?P<host_ip>[^:]+):(?P<host>\d+)")
VOLUME_RE = re.compile(r"- (?P<host>/[^\s]+) -> (?P<container>/[^\s]+)")
NETWORK_RE = re.compile(r"- (?P<name>ix-[^\s]+)_default")
SUBNET_RE = re.compile(r"subnets=\[(?P<subnets>[^\]]+)\]")
MODELS_RE = re.compile(r"Models in /models: (?P<models>.+)$")
PORTAL_RE = re.compile(r"Portals: \{\'Web UI\': \'(?P<url>[^\']+)\'\}")
GPU_RE = re.compile(r"GPUs:\s*(?P<count>\d+)x\s*(?P<name>.+)$")
CONTAINER_NAME_RE = re.compile(r"^(?P<name>ix-llamacpp-[^\s]+)")
@dataclass
class LlamacppConfig:
image: Optional[str] = None
container_name: Optional[str] = None
host_port: Optional[int] = None
container_port: Optional[int] = None
web_ui_url: Optional[str] = None
model_host_path: Optional[str] = None
model_container_path: Optional[str] = None
models: List[str] = None
network: Optional[str] = None
subnets: List[str] = None
gpu_count: Optional[int] = None
gpu_name: Optional[str] = None
def _find_section(lines: List[str], app_name: str) -> List[str]:
start = None
for i, line in enumerate(lines):
m = APP_HEADER_RE.match(line.strip())
if m and m.group("name") == app_name:
start = i
break
if start is None:
return []
for j in range(start + 1, len(lines)):
if APP_HEADER_RE.match(lines[j].strip()):
return lines[start:j]
return lines[start:]
def parse_agents(path: Path) -> LlamacppConfig:
text = path.read_text(encoding="utf-8", errors="ignore")
lines = text.splitlines()
section = _find_section(lines, "llamacpp")
cfg = LlamacppConfig(models=[], subnets=[])
for line in section:
if cfg.image is None:
m = IMAGE_RE.search(line)
if m:
cfg.image = m.group("image")
if cfg.web_ui_url is None:
m = PORTAL_RE.search(line)
if m:
cfg.web_ui_url = m.group("url")
if cfg.container_port is None or cfg.host_port is None:
m = PORT_LINE_RE.search(line)
if m:
cfg.container_port = int(m.group("container"))
cfg.host_port = int(m.group("host"))
if cfg.model_host_path is None or cfg.model_container_path is None:
m = VOLUME_RE.search(line)
if m and "/models" in m.group("container"):
cfg.model_host_path = m.group("host")
cfg.model_container_path = m.group("container")
if cfg.network is None:
m = NETWORK_RE.search(line)
if m:
cfg.network = f"{m.group('name')}_default"
if "subnets=" in line:
m = SUBNET_RE.search(line)
if m:
subnets_raw = m.group("subnets")
subnets = [s.strip().strip("'") for s in subnets_raw.split(",")]
cfg.subnets.extend([s for s in subnets if s])
if "Models in /models:" in line:
m = MODELS_RE.search(line)
if m:
models_raw = m.group("models")
cfg.models = [s.strip() for s in models_raw.split(",") if s.strip()]
for line in lines:
if cfg.gpu_count is None:
m = GPU_RE.search(line)
if m:
cfg.gpu_count = int(m.group("count"))
cfg.gpu_name = m.group("name").strip()
if cfg.container_name is None:
m = CONTAINER_NAME_RE.match(line.strip())
if m:
cfg.container_name = m.group("name")
return cfg
def main() -> None:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--agents", default="AGENTS.md")
parser.add_argument("--out", default="app/agents_config.json")
args = parser.parse_args()
cfg = parse_agents(Path(args.agents))
out_path = Path(args.out)
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(asdict(cfg), indent=2), encoding="utf-8")
if __name__ == "__main__":
main()