subcortex 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- subcortex/__init__.py +3 -0
- subcortex/__main__.py +3 -0
- subcortex/adapters/__init__.py +48 -0
- subcortex/adapters/base.py +230 -0
- subcortex/adapters/claude_family.py +133 -0
- subcortex/adapters/codex.py +87 -0
- subcortex/adapters/copilot.py +60 -0
- subcortex/adapters/cursor.py +36 -0
- subcortex/adapters/docker_agent.py +115 -0
- subcortex/adapters/gemini_family.py +60 -0
- subcortex/adapters/grok.py +98 -0
- subcortex/adapters/kimi_code.py +138 -0
- subcortex/adapters/letta_vibe.py +96 -0
- subcortex/adapters/openhands.py +153 -0
- subcortex/auth.py +59 -0
- subcortex/backends/__init__.py +23 -0
- subcortex/backends/base.py +22 -0
- subcortex/backends/jev.py +460 -0
- subcortex/backends/laya.py +149 -0
- subcortex/cli.py +809 -0
- subcortex/client.py +77 -0
- subcortex/config.py +263 -0
- subcortex/daemon.py +502 -0
- subcortex/evalset.py +241 -0
- subcortex/hook.py +254 -0
- subcortex/installers/__init__.py +62 -0
- subcortex/installers/amp.py +39 -0
- subcortex/installers/base.py +874 -0
- subcortex/installers/claude_family.py +229 -0
- subcortex/installers/codex.py +110 -0
- subcortex/installers/copilot.py +65 -0
- subcortex/installers/crush.py +36 -0
- subcortex/installers/cursor.py +79 -0
- subcortex/installers/gemini_family.py +83 -0
- subcortex/installers/goose.py +186 -0
- subcortex/installers/kimi_code.py +71 -0
- subcortex/installers/mcp_only.py +111 -0
- subcortex/installers/more_hooks.py +184 -0
- subcortex/installers/opencode.py +66 -0
- subcortex/installers/openhands.py +84 -0
- subcortex/installers/pi_cline.py +53 -0
- subcortex/ledger.py +92 -0
- subcortex/localhttp.py +59 -0
- subcortex/mcp_server.py +187 -0
- subcortex/metrics.py +56 -0
- subcortex/plugins/amp/subcortex.ts +258 -0
- subcortex/plugins/cline/subcortex.ts +340 -0
- subcortex/plugins/opencode/subcortex.ts +265 -0
- subcortex/plugins/pi/subcortex.ts +292 -0
- subcortex/policy.py +341 -0
- subcortex/presets.py +163 -0
- subcortex/provision.py +188 -0
- subcortex/service.py +149 -0
- subcortex/state.py +137 -0
- subcortex/transcript.py +211 -0
- subcortex/tuis.py +51 -0
- subcortex/ui.py +319 -0
- subcortex/verdicts.py +233 -0
- subcortex/wizard.py +474 -0
- subcortex-0.3.0.dist-info/METADATA +287 -0
- subcortex-0.3.0.dist-info/RECORD +64 -0
- subcortex-0.3.0.dist-info/WHEEL +5 -0
- subcortex-0.3.0.dist-info/entry_points.txt +3 -0
- subcortex-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Laya backend: local typed-decision models.
|
|
2
|
+
|
|
3
|
+
Ported from laya-hermes (hermes_laya/backend.py), trimmed to the two backends
|
|
4
|
+
subcortex supports:
|
|
5
|
+
|
|
6
|
+
- ``mlx`` — ``pip install laya-mlx`` (Apple Silicon, macOS 14+)
|
|
7
|
+
- ``torch`` — ``pip install laya`` (upstream PyTorch, CUDA/CPU; everywhere else)
|
|
8
|
+
|
|
9
|
+
Model aliases: ``english`` (421M, 512 tok), ``multilingual`` (322M, 100+
|
|
10
|
+
languages, 1024 tok — default), ``typed-decisions`` (421M fine-tuned for
|
|
11
|
+
typed-decision workflows, 1024 tok).
|
|
12
|
+
|
|
13
|
+
Agents load lazily on first ``predict`` and are cached per (backend, alias).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import importlib
|
|
19
|
+
import importlib.util
|
|
20
|
+
import os
|
|
21
|
+
import platform
|
|
22
|
+
import sys
|
|
23
|
+
import threading
|
|
24
|
+
from typing import Any, Dict, Tuple
|
|
25
|
+
|
|
26
|
+
from ..config import venv_pip
|
|
27
|
+
|
|
28
|
+
_BACKENDS = ("mlx", "torch")
|
|
29
|
+
_MODULES = {"mlx": "laya_mlx", "torch": "laya"}
|
|
30
|
+
_PACKAGES = {"mlx": "laya-mlx", "torch": "laya"}
|
|
31
|
+
|
|
32
|
+
# alias -> per-backend Hugging Face checkpoint (torch uses repo + optional subfolder)
|
|
33
|
+
_CHECKPOINTS: Dict[str, Dict[str, Any]] = {
|
|
34
|
+
"english": {
|
|
35
|
+
"mlx": "aac6fef/laya-mlx",
|
|
36
|
+
"torch": ("convaiinnovations/laya", None),
|
|
37
|
+
},
|
|
38
|
+
"multilingual": {
|
|
39
|
+
"mlx": "aac6fef/laya-multilingual-mlx",
|
|
40
|
+
"torch": ("convaiinnovations/laya", "multilingual"),
|
|
41
|
+
},
|
|
42
|
+
"typed-decisions": {
|
|
43
|
+
"mlx": "aac6fef/laya-typed-decisions-mlx",
|
|
44
|
+
"torch": ("convaiinnovations/laya", "typed-decisions"),
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
DEFAULT_MODEL = "multilingual"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class BackendUnavailableError(RuntimeError):
|
|
52
|
+
"""Raised when the laya backend package is not installed/importable."""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _is_apple_silicon() -> bool:
|
|
56
|
+
return sys.platform == "darwin" and platform.machine() == "arm64"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def detect_backend() -> str:
|
|
60
|
+
"""mlx on Apple Silicon, torch everywhere else."""
|
|
61
|
+
return "mlx" if _is_apple_silicon() else "torch"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _module_available(module: str) -> bool:
|
|
65
|
+
try:
|
|
66
|
+
return importlib.util.find_spec(module) is not None
|
|
67
|
+
except (ImportError, ValueError):
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _install_hint(package: str) -> str:
|
|
72
|
+
if venv_pip().exists():
|
|
73
|
+
return f"run: {venv_pip()} install {package}"
|
|
74
|
+
return f"run: {sys.executable} -m pip install {package}"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class LayaBackend:
|
|
78
|
+
name = "laya"
|
|
79
|
+
|
|
80
|
+
def __init__(self, config: Dict[str, Any]) -> None:
|
|
81
|
+
self.model = str(config.get("model") or DEFAULT_MODEL).strip().lower()
|
|
82
|
+
self._agents: Dict[Tuple[str, str], Any] = {}
|
|
83
|
+
self._lock = threading.Lock()
|
|
84
|
+
# The daemon serves requests on many threads, but a model is not safe to
|
|
85
|
+
# run concurrently (MLX shares one Metal queue; torch modules keep state).
|
|
86
|
+
self._predict_lock = threading.Lock()
|
|
87
|
+
|
|
88
|
+
# -- availability ---------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
def available(self) -> Tuple[bool, str]:
|
|
91
|
+
backend = detect_backend()
|
|
92
|
+
module = _MODULES[backend]
|
|
93
|
+
if _module_available(module):
|
|
94
|
+
return True, f"laya backend {backend!r} available ({module}, model {self.model!r})"
|
|
95
|
+
package = _PACKAGES[backend]
|
|
96
|
+
return False, (
|
|
97
|
+
f"{module} is not installed for {sys.executable}. "
|
|
98
|
+
f"{_install_hint(package)}"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# -- lazy loading ---------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
def _checkpoint(self, alias: str, backend: str) -> Any:
|
|
104
|
+
if alias not in _CHECKPOINTS:
|
|
105
|
+
raise ValueError(
|
|
106
|
+
f"Unknown laya model alias {alias!r}; valid: {', '.join(sorted(_CHECKPOINTS))}"
|
|
107
|
+
)
|
|
108
|
+
return _CHECKPOINTS[alias][backend]
|
|
109
|
+
|
|
110
|
+
def _load(self, alias: str) -> Any:
|
|
111
|
+
backend = detect_backend()
|
|
112
|
+
key = (backend, alias)
|
|
113
|
+
with self._lock:
|
|
114
|
+
if key in self._agents:
|
|
115
|
+
return self._agents[key]
|
|
116
|
+
module = _MODULES[backend]
|
|
117
|
+
if not _module_available(module):
|
|
118
|
+
raise BackendUnavailableError(
|
|
119
|
+
f"Laya backend {backend!r} is not available. "
|
|
120
|
+
f"{_install_hint(_PACKAGES[backend])}"
|
|
121
|
+
)
|
|
122
|
+
try:
|
|
123
|
+
if backend == "torch":
|
|
124
|
+
# Upstream laya deadlocks on import when TensorFlow is also installed.
|
|
125
|
+
os.environ.setdefault("USE_TF", "0")
|
|
126
|
+
mod = importlib.import_module(module)
|
|
127
|
+
except ImportError as exc:
|
|
128
|
+
raise BackendUnavailableError(
|
|
129
|
+
f"Laya backend {backend!r} failed to import ({exc}). "
|
|
130
|
+
f"{_install_hint(_PACKAGES[backend])}"
|
|
131
|
+
) from exc
|
|
132
|
+
ref = self._checkpoint(alias, backend)
|
|
133
|
+
if backend == "mlx":
|
|
134
|
+
agent = mod.load(ref)
|
|
135
|
+
else: # torch
|
|
136
|
+
repo, subfolder = ref
|
|
137
|
+
agent = mod.load(repo, subfolder=subfolder) if subfolder else mod.load(repo)
|
|
138
|
+
self._agents[key] = agent
|
|
139
|
+
return agent
|
|
140
|
+
|
|
141
|
+
# -- inference ------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
def predict(self, state: Any, questions: Dict[str, Any]) -> Dict[str, Any]:
|
|
144
|
+
agent = self._load(self.model)
|
|
145
|
+
with self._predict_lock:
|
|
146
|
+
result = agent.predict(state, questions)
|
|
147
|
+
if not isinstance(result, dict):
|
|
148
|
+
result = {"answers": result}
|
|
149
|
+
return result
|