ltcai 11.5.0 → 11.5.2

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.
Files changed (182) hide show
  1. package/README.md +86 -47
  2. package/docs/CHANGELOG.md +76 -0
  3. package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
  4. package/docs/DEVELOPMENT.md +1 -1
  5. package/docs/HYBRID_CLOUD_KG_STREAMING.md +8 -4
  6. package/docs/MULTI_AGENT_RUNTIME.md +1 -1
  7. package/docs/ONBOARDING.md +1 -1
  8. package/docs/OPERATIONS.md +1 -1
  9. package/docs/TRUST_MODEL.md +1 -1
  10. package/docs/WHY_LATTICE.md +1 -1
  11. package/docs/WORKFLOW_DESIGNER.md +1 -1
  12. package/docs/architecture.md +4 -4
  13. package/docs/kg-schema.md +1 -1
  14. package/docs/v11.5.1_RUST_FULL_LOOP_PLAN.md +85 -0
  15. package/docs/v11.5.2_TIGHT_SHIP_PLAN.md +144 -0
  16. package/lattice_brain/__init__.py +1 -1
  17. package/lattice_brain/graph/proactive.py +1 -1
  18. package/lattice_brain/graph/projection/v2_schema.py +0 -28
  19. package/lattice_brain/graph/vector_index/base.py +0 -3
  20. package/lattice_brain/graph/vector_index/brute_force.py +0 -4
  21. package/lattice_brain/graph/vector_index/hnsw.py +0 -3
  22. package/lattice_brain/graph/vector_index/quantized.py +0 -3
  23. package/lattice_brain/ingestion_jobs.py +0 -3
  24. package/lattice_brain/memory.py +0 -27
  25. package/lattice_brain/portability/sharing.py +0 -4
  26. package/lattice_brain/quality.py +0 -49
  27. package/lattice_brain/runtime/agent_runtime.py +1 -1
  28. package/lattice_brain/runtime/hooks.py +0 -13
  29. package/lattice_brain/runtime/multi_agent.py +1 -1
  30. package/lattice_brain/sealed_box.py +0 -4
  31. package/latticeai/__init__.py +1 -1
  32. package/latticeai/api/admin.py +12 -12
  33. package/latticeai/api/agent_worker_seam.py +389 -0
  34. package/latticeai/api/auth.py +25 -2
  35. package/latticeai/api/chat.py +3 -3
  36. package/latticeai/api/chat_agent_http.py +3 -12
  37. package/latticeai/api/chat_helpers.py +10 -13
  38. package/latticeai/api/computer_use.py +31 -42
  39. package/latticeai/api/health.py +21 -1
  40. package/latticeai/api/local_files.py +14 -0
  41. package/latticeai/api/permissions.py +38 -12
  42. package/latticeai/api/search.py +24 -0
  43. package/latticeai/api/static_routes.py +4 -1
  44. package/latticeai/cli/runtime.py +7 -3
  45. package/latticeai/core/config.py +47 -3
  46. package/latticeai/core/csrf.py +28 -2
  47. package/latticeai/core/embedding_providers/__init__.py +3 -5
  48. package/latticeai/core/embedding_providers/base.py +1 -1
  49. package/latticeai/core/embedding_providers/text.py +1 -1
  50. package/latticeai/core/enterprise.py +1 -4
  51. package/latticeai/core/http_origin.py +146 -0
  52. package/latticeai/core/invitations.py +3 -2
  53. package/latticeai/core/io_utils.py +2 -11
  54. package/latticeai/core/legacy_compatibility.py +1 -1
  55. package/latticeai/core/marketplace.py +1 -1
  56. package/latticeai/core/messages.py +40 -0
  57. package/latticeai/core/model_compat.py +2 -8
  58. package/latticeai/core/module_probe.py +37 -0
  59. package/latticeai/core/run_explain.py +5 -2
  60. package/latticeai/core/run_store.py +2 -2
  61. package/latticeai/core/security.py +13 -0
  62. package/latticeai/core/sessions.py +3 -2
  63. package/latticeai/core/sse.py +25 -0
  64. package/latticeai/core/users.py +0 -9
  65. package/latticeai/core/workspace_graph_trace.py +2 -8
  66. package/latticeai/core/workspace_os.py +0 -10
  67. package/latticeai/core/workspace_os_constants.py +1 -1
  68. package/latticeai/core/workspace_os_utils.py +34 -0
  69. package/latticeai/core/workspace_review_items.py +2 -8
  70. package/latticeai/core/workspace_runs.py +2 -8
  71. package/latticeai/core/workspace_skills.py +2 -7
  72. package/latticeai/models/router/documents.py +0 -35
  73. package/latticeai/models/router/registry.py +0 -4
  74. package/latticeai/runtime/access_runtime.py +16 -4
  75. package/latticeai/runtime/build_phases/features.py +25 -2
  76. package/latticeai/runtime/build_phases/web.py +2 -0
  77. package/latticeai/runtime/feature_toggle_wiring.py +12 -18
  78. package/latticeai/runtime/network_boundary_wiring.py +8 -19
  79. package/latticeai/runtime/permission_mode_wiring.py +6 -13
  80. package/latticeai/runtime/router_registration.py +4 -0
  81. package/latticeai/runtime/runtime_context.py +1 -13
  82. package/latticeai/runtime/service_singletons.py +55 -0
  83. package/latticeai/services/architecture_readiness.py +1 -1
  84. package/latticeai/services/brain_intelligence/proposals.py +0 -5
  85. package/latticeai/services/change_proposals.py +2 -36
  86. package/latticeai/services/chronicle.py +4 -6
  87. package/latticeai/services/command_center.py +4 -4
  88. package/latticeai/services/evidence_actions.py +5 -2
  89. package/latticeai/services/hybrid_chat.py +2 -2
  90. package/latticeai/services/hybrid_policy.py +8 -57
  91. package/latticeai/services/mode_store.py +132 -0
  92. package/latticeai/services/model_capability_registry.py +0 -15
  93. package/latticeai/services/model_catalog.py +0 -7
  94. package/latticeai/services/model_loading.py +3 -2
  95. package/latticeai/services/model_runtime/__init__.py +0 -57
  96. package/latticeai/services/model_runtime/engines.py +27 -128
  97. package/latticeai/services/model_runtime/loading.py +2 -2
  98. package/latticeai/services/network_boundary_service.py +7 -44
  99. package/latticeai/services/permission_mode_service.py +8 -56
  100. package/latticeai/services/product_readiness.py +1 -1
  101. package/latticeai/services/setup_detection.py +67 -1
  102. package/latticeai/services/tool_dispatch.py +0 -4
  103. package/latticeai/services/upload_service.py +7 -20
  104. package/latticeai/services/workspace_service.py +0 -6
  105. package/latticeai/setup/auto_setup.py +15 -26
  106. package/latticeai/setup/wizard/catalog.py +5 -2
  107. package/latticeai/setup/wizard/detect.py +6 -27
  108. package/latticeai/setup/wizard/paths.py +2 -5
  109. package/latticeai/setup/wizard/plans.py +2 -2
  110. package/package.json +2 -4
  111. package/scripts/bump_version.py +14 -1
  112. package/scripts/check_current_release_docs.mjs +1 -1
  113. package/scripts/check_legacy_debt.mjs +1 -1
  114. package/scripts/check_server_i18n.mjs +1 -0
  115. package/scripts/generate_agent_loop_fixtures.py +994 -0
  116. package/scripts/generate_rust_parity_fixtures.py +72 -161
  117. package/scripts/parity_fixture_corpus_context.py +162 -0
  118. package/scripts/parity_fixture_corpus_docgen.py +341 -0
  119. package/scripts/release_screen_claims.json +20 -0
  120. package/src-tauri/Cargo.lock +13 -7
  121. package/src-tauri/Cargo.toml +1 -1
  122. package/src-tauri/src/backend.rs +76 -2
  123. package/src-tauri/src/main.rs +13 -3
  124. package/src-tauri/tauri.conf.json +2 -2
  125. package/static/app/asset-manifest.json +41 -41
  126. package/static/app/assets/{Act-CWnxSCgN.js → Act-DcQizkl1.js} +1 -1
  127. package/static/app/assets/{AdminConsole-BEQYU6kF.js → AdminConsole-cf4npybT.js} +1 -1
  128. package/static/app/assets/{Brain-DWu1BhFg.js → Brain-3VCSHFcn.js} +1 -1
  129. package/static/app/assets/{BrainHome-95Hilr9R.js → BrainHome-Qm8eaztx.js} +1 -1
  130. package/static/app/assets/{BrainSignals-QdeqCpAF.js → BrainSignals-DS9BtKOW.js} +1 -1
  131. package/static/app/assets/{Capture-BHpCxnzb.js → Capture-DiQ219jW.js} +1 -1
  132. package/static/app/assets/{Chronicle-B4xYKoed.js → Chronicle-BGvuAchH.js} +1 -1
  133. package/static/app/assets/{CommandPalette-BVXnttSz.js → CommandPalette-Bqhm0Urn.js} +1 -1
  134. package/static/app/assets/{Library-DgYcHome.js → Library-BV6NnF0a.js} +1 -1
  135. package/static/app/assets/{LivingBrain-CrJLDbf7.js → LivingBrain-GzenJchP.js} +1 -1
  136. package/static/app/assets/{ProductFlow-DFlScKoJ.js → ProductFlow-DEP6-vML.js} +1 -1
  137. package/static/app/assets/{ReviewCard-Cy5f48Pj.js → ReviewCard-CNZ7XjWG.js} +1 -1
  138. package/static/app/assets/{System-NF8IfhTa.js → System-CieofHQa.js} +1 -1
  139. package/static/app/assets/arrow-left-kfsrk0mv.js +1 -0
  140. package/static/app/assets/{bot-CucuhLhm.js → bot-B_K1Tdmw.js} +1 -1
  141. package/static/app/assets/{brain-BBnSryW_.js → brain-DWyaV1L1.js} +1 -1
  142. package/static/app/assets/{button-C2GUj2Ai.js → button-aTn4s84A.js} +1 -1
  143. package/static/app/assets/circle-check-qqLug9nU.js +1 -0
  144. package/static/app/assets/{circle-pause-CbkWzBmG.js → circle-pause-xKgeGXkT.js} +1 -1
  145. package/static/app/assets/{circle-play-7lEaqHdJ.js → circle-play-DkT6tYPX.js} +1 -1
  146. package/static/app/assets/{cpu-DAlCXlIy.js → cpu-85xYObUC.js} +1 -1
  147. package/static/app/assets/{download-RNhuuJwh.js → download-B5Fm7YXo.js} +1 -1
  148. package/static/app/assets/{folder-open-CLW4odzM.js → folder-open-kk2Xa52u.js} +1 -1
  149. package/static/app/assets/{hard-drive-NKEiDIAJ.js → hard-drive-DkA3zBW_.js} +1 -1
  150. package/static/app/assets/{index-DMurvUuR.js → index-BMPdTmlY.js} +3 -3
  151. package/static/app/assets/index-DxmOfNRi.css +2 -0
  152. package/static/app/assets/{input-D2UhPC1X.js → input-B0nRf2jO.js} +1 -1
  153. package/static/app/assets/{link-2-6amKbP_P.js → link-2-Dwb4gnTc.js} +1 -1
  154. package/static/app/assets/{permissionCopy-Cu9TZtdR.js → permissionCopy-CQDUBrOZ.js} +1 -1
  155. package/static/app/assets/{primitives-gPsccucr.js → primitives-SNp0LRJz.js} +1 -1
  156. package/static/app/assets/search-BcHqkjoy.js +1 -0
  157. package/static/app/assets/{share-2-Bau7KkPq.js → share-2-BsrxFglO.js} +1 -1
  158. package/static/app/assets/{shield-alert-BufNYypi.js → shield-alert-5BStfp2_.js} +1 -1
  159. package/static/app/assets/{textarea-BQnVWhYs.js → textarea-Cg8IUA-k.js} +1 -1
  160. package/static/app/assets/{useFocusTrap-B3_w60si.js → useFocusTrap-CYKvE46M.js} +1 -1
  161. package/static/app/assets/{useMutation-BHhCflT6.js → useMutation-CSn9t1op.js} +1 -1
  162. package/static/app/assets/{useQuery-rBWfI-5t.js → useQuery-CY2OI2uy.js} +1 -1
  163. package/static/app/assets/{utils-V_5-wxr5.js → utils-Ddol2RWD.js} +1 -1
  164. package/static/app/assets/{workspace-K1zjYUHj.js → workspace-BqDwOz_p.js} +1 -1
  165. package/static/app/index.html +4 -4
  166. package/static/sw.js +1 -1
  167. package/desktop/electron/README.md +0 -9
  168. package/desktop/electron/main.cjs +0 -58
  169. package/desktop/electron/preload.cjs +0 -5
  170. package/latticeai/core/graph_curator.py +0 -11
  171. package/latticeai/core/hooks.py +0 -11
  172. package/latticeai/core/local_embeddings.py +0 -104
  173. package/latticeai/core/multi_agent.py +0 -11
  174. package/latticeai/core/workflow_engine.py +0 -11
  175. package/latticeai/services/ingestion.py +0 -11
  176. package/latticeai/services/kg_portability.py +0 -11
  177. package/latticeai/services/multimodal_streaming.py +0 -129
  178. package/scripts/measure_brain_home_fill.mjs +0 -141
  179. package/static/app/assets/arrow-left-DwkSYrjR.js +0 -1
  180. package/static/app/assets/circle-check-CxOVPwYq.js +0 -1
  181. package/static/app/assets/index-BLPb5lmE.css +0 -2
  182. package/static/app/assets/search-Cj_TKk_2.js +0 -1
@@ -12,16 +12,13 @@ Persisted under the data dir, scoped like NetworkBoundaryService
12
12
 
13
13
  from __future__ import annotations
14
14
 
15
- import json
16
- import threading
17
- from pathlib import Path
18
- from typing import Any, Callable, Dict, List, Optional, Set
15
+ from typing import Any, Dict, List, Optional, Set
19
16
 
20
- from latticeai.core.io_utils import atomic_write_json
21
17
  from latticeai.core.network_boundary import (
22
18
  HARD_BLOCK_METADATA_FLAGS,
23
19
  HARD_BLOCK_NODE_TYPES,
24
20
  )
21
+ from latticeai.services.mode_store import JsonBackedModeService
25
22
 
26
23
  DEFAULT_BLOCKED_TYPES: List[str] = []
27
24
  DEFAULT_BLOCKED_FLAGS: List[str] = sorted(HARD_BLOCK_METADATA_FLAGS)
@@ -39,51 +36,15 @@ def _default_policy() -> Dict[str, Any]:
39
36
  }
40
37
 
41
38
 
42
- class HybridPolicyService:
39
+ class HybridPolicyService(JsonBackedModeService[Dict[str, Any]]):
43
40
  """Load / save hybrid cloud policy."""
44
41
 
45
- def __init__(
46
- self,
47
- *,
48
- data_dir: Path,
49
- audit: Optional[Callable[..., None]] = None,
50
- ) -> None:
51
- self._path = Path(data_dir) / "hybrid_policy.json"
52
- self._audit = audit or (lambda *a, **kw: None)
53
- self._lock = threading.Lock()
54
-
55
- def rebind_data_dir(self, data_dir: Path) -> None:
56
- with self._lock:
57
- self._path = Path(data_dir) / "hybrid_policy.json"
42
+ FILENAME = "hybrid_policy.json"
58
43
 
59
- def rebind_audit(self, audit: Callable[..., None]) -> None:
60
- with self._lock:
61
- self._audit = audit
44
+ def _default_entry(self) -> Any:
45
+ return _default_policy()
62
46
 
63
- def _read(self) -> Dict[str, Any]:
64
- base = {
65
- "default": _default_policy(),
66
- "users": {},
67
- "workspaces": {},
68
- }
69
- if not self._path.exists():
70
- return base
71
- try:
72
- data = json.loads(self._path.read_text(encoding="utf-8"))
73
- except Exception:
74
- return base
75
- if not isinstance(data, dict):
76
- return base
77
- data.setdefault("default", _default_policy())
78
- data.setdefault("users", {})
79
- data.setdefault("workspaces", {})
80
- return data
81
-
82
- def _write(self, data: Dict[str, Any]) -> None:
83
- self._path.parent.mkdir(parents=True, exist_ok=True)
84
- atomic_write_json(self._path, data)
85
-
86
- def _resolve_raw(
47
+ def _resolve_from(
87
48
  self,
88
49
  data: Dict[str, Any],
89
50
  *,
@@ -116,16 +77,6 @@ class HybridPolicyService:
116
77
  policy["min_extraction_confidence"] = 0.55
117
78
  return policy
118
79
 
119
- def resolve(
120
- self,
121
- *,
122
- user_email: Optional[str] = None,
123
- workspace_id: Optional[str] = None,
124
- ) -> Dict[str, Any]:
125
- with self._lock:
126
- data = self._read()
127
- return self._resolve_raw(data, user_email=user_email, workspace_id=workspace_id)
128
-
129
80
  def set_policy(
130
81
  self,
131
82
  patch: Dict[str, Any],
@@ -144,7 +95,7 @@ class HybridPolicyService:
144
95
  clean = {k: v for k, v in (patch or {}).items() if k in allowed}
145
96
  with self._lock:
146
97
  data = self._read()
147
- previous = self._resolve_raw(
98
+ previous = self._resolve_from(
148
99
  data, user_email=user_email, workspace_id=workspace_id
149
100
  )
150
101
  if workspace_id:
@@ -0,0 +1,132 @@
1
+ """The shared storage half of the scoped preference dials.
2
+
3
+ ``PermissionModeService``, ``NetworkBoundaryService`` and ``HybridPolicyService``
4
+ are three different policies over one identical store: a JSON file under the
5
+ data dir holding ``{"default": …, "users": {…}, "workspaces": {…}}``, guarded by
6
+ a lock, rebindable after lazy construction, and read defensively enough that a
7
+ truncated or hand-edited file degrades to defaults instead of taking the app
8
+ down.
9
+
10
+ That half was written three times, character for character. Three copies of a
11
+ "corrupt file falls back to defaults" rule is three chances for one of them to
12
+ grow an exception path the others do not have — and the symptom would be one
13
+ dial silently forgetting a user's choice while the other two remember. The
14
+ storage lives here once; each service supplies only what actually differs:
15
+ the file name, what an unset ``default`` means, and how a scope resolves.
16
+
17
+ Deliberately *not* shared: ``set_mode``/``set_policy``. They look similar but
18
+ their write shapes differ (a scalar mode replaces, a policy patch merges) and
19
+ their audit events carry different payloads, so folding them together would
20
+ mean a parameterised method with more branches than the two bodies it replaced.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import json
26
+ import threading
27
+ from pathlib import Path
28
+ from typing import Any, Callable, ClassVar, Dict, Generic, Optional, TypeVar
29
+
30
+ from latticeai.core.io_utils import atomic_write_json
31
+
32
+ #: What ``resolve`` hands back — an enum member for the mode dials, a plain
33
+ #: dict for the policy service.
34
+ ResolvedT = TypeVar("ResolvedT")
35
+
36
+ __all__ = ["JsonBackedModeService"]
37
+
38
+
39
+ class JsonBackedModeService(Generic[ResolvedT]):
40
+ """A lock-guarded, scope-aware JSON preference file.
41
+
42
+ Subclasses set :attr:`FILENAME` and implement :meth:`_default_entry` and
43
+ :meth:`_resolve_from`.
44
+ """
45
+
46
+ #: File name under the data dir. Subclasses must set this.
47
+ FILENAME: ClassVar[str] = ""
48
+
49
+ def __init__(
50
+ self,
51
+ *,
52
+ data_dir: Path,
53
+ audit: Optional[Callable[..., None]] = None,
54
+ ) -> None:
55
+ self._path = Path(data_dir) / self.FILENAME
56
+ self._audit = audit or (lambda *a, **kw: None)
57
+ self._lock = threading.Lock()
58
+
59
+ # ── rebinding ────────────────────────────────────────────────────────────
60
+ def rebind_data_dir(self, data_dir: Path) -> None:
61
+ """Point the store at the app's real data dir.
62
+
63
+ The wiring may instantiate a service lazily before routers know the
64
+ configured data dir; rebinding keeps one file of record instead of
65
+ stranding writes under the fallback path.
66
+ """
67
+ with self._lock:
68
+ self._path = Path(data_dir) / self.FILENAME
69
+
70
+ def rebind_audit(self, audit: Callable[..., None]) -> None:
71
+ """Attach the real audit sink once app wiring provides one."""
72
+ with self._lock:
73
+ self._audit = audit
74
+
75
+ # ── what each subclass must decide ───────────────────────────────────────
76
+ def _default_entry(self) -> Any:
77
+ """The ``default`` bucket's value when the file does not say.
78
+
79
+ Called fresh at every use because the policy service's answer is a
80
+ mutable dict; a shared instance would let one caller's edit leak into
81
+ the next caller's defaults.
82
+ """
83
+ raise NotImplementedError
84
+
85
+ def _resolve_from(
86
+ self,
87
+ data: Dict[str, Any],
88
+ *,
89
+ user_email: Optional[str],
90
+ workspace_id: Optional[str],
91
+ ) -> ResolvedT:
92
+ """Apply this dial's scope precedence to already-loaded ``data``.
93
+
94
+ Pure over ``data`` and lock-free, so holders of ``_lock`` can reuse it
95
+ without re-entering a non-reentrant lock.
96
+ """
97
+ raise NotImplementedError
98
+
99
+ # ── storage ──────────────────────────────────────────────────────────────
100
+ def _empty(self) -> Dict[str, Any]:
101
+ return {"default": self._default_entry(), "users": {}, "workspaces": {}}
102
+
103
+ def _read(self) -> Dict[str, Any]:
104
+ if not self._path.exists():
105
+ return self._empty()
106
+ try:
107
+ data = json.loads(self._path.read_text(encoding="utf-8"))
108
+ except Exception:
109
+ return self._empty()
110
+ if not isinstance(data, dict):
111
+ return self._empty()
112
+ data.setdefault("default", self._default_entry())
113
+ data.setdefault("users", {})
114
+ data.setdefault("workspaces", {})
115
+ return data
116
+
117
+ def _write(self, data: Dict[str, Any]) -> None:
118
+ self._path.parent.mkdir(parents=True, exist_ok=True)
119
+ atomic_write_json(self._path, data)
120
+
121
+ # ── reading ──────────────────────────────────────────────────────────────
122
+ def resolve(
123
+ self,
124
+ *,
125
+ user_email: Optional[str] = None,
126
+ workspace_id: Optional[str] = None,
127
+ ) -> ResolvedT:
128
+ with self._lock:
129
+ data = self._read()
130
+ return self._resolve_from(
131
+ data, user_email=user_email, workspace_id=workspace_id,
132
+ )
@@ -636,11 +636,6 @@ def get_all_capabilities() -> List[ModelCapability]:
636
636
  return [*_REGISTRY, *_LEGACY_REGISTRY]
637
637
 
638
638
 
639
- def get_recommended_capabilities() -> List[ModelCapability]:
640
- """Current-generation entries: catalog, download and recommendation input."""
641
- return list(_REGISTRY)
642
-
643
-
644
639
  def get_legacy_capabilities() -> List[ModelCapability]:
645
640
  """Recognised-only entries: never offered, never recommended, still named."""
646
641
  return list(_LEGACY_REGISTRY)
@@ -663,12 +658,6 @@ def is_recognized_model(model_id: str) -> bool:
663
658
  return get_capability(model_id) is not None
664
659
 
665
660
 
666
- def is_recommended_model(model_id: str) -> bool:
667
- """True only for current-generation entries — the download/offer gate."""
668
- cap = get_capability(model_id)
669
- return cap is not None and cap.lifecycle == RECOMMENDED
670
-
671
-
672
661
  def build_engine_model_catalog() -> Dict[str, List[Dict[str, Any]]]:
673
662
  """Return legacy ENGINE_MODEL_CATALOG shape, enriched with rich fields.
674
663
 
@@ -717,7 +706,3 @@ def get_verified_models() -> List[Dict[str, Any]]:
717
706
  if c.verification.hf_exists and c.verification.has_config and c.verification.has_tokenizer
718
707
  ]
719
708
 
720
-
721
- # Back-compat: expose a simple list mirroring the old top-level for mlx.
722
- # Recommended entries only — same reasoning as build_engine_model_catalog.
723
- LOCAL_MLX_MODELS = [c.to_legacy_dict() for c in _REGISTRY if "local_mlx" in c.provider_hints]
@@ -15,10 +15,6 @@ import re
15
15
  import sys
16
16
  from typing import Any, Dict, List, Optional
17
17
 
18
- from latticeai.services.model_capability_registry import (
19
- LOCAL_MLX_MODELS as _LOCAL_MLX_MODELS,
20
- )
21
-
22
18
  # 5.2.0: Delegate catalog data to the structured capability registry (rich + verified).
23
19
  # This keeps backward compat for every `from ...model_catalog import ENGINE_MODEL_CATALOG`.
24
20
  from latticeai.services.model_capability_registry import (
@@ -178,9 +174,6 @@ get_all_capabilities = _get_all_capabilities
178
174
  get_capability = _get_capability
179
175
  get_verified_models = _get_verified_models
180
176
 
181
- # Convenience re-export for tests / places that did `from ...model_catalog import LOCAL_MLX_MODELS`
182
- LOCAL_MLX_MODELS = _LOCAL_MLX_MODELS # type: ignore[name-defined]
183
-
184
177
  _VERSIONED_MODEL_PATTERNS = (
185
178
  ("gemma", re.compile(r"\bgemma[-\s]?(\d+(?:\.\d+)?)", re.IGNORECASE)),
186
179
  ("qwen", re.compile(r"\bqwen[-\s]?(\d+(?:\.\d+)?)", re.IGNORECASE)),
@@ -6,7 +6,6 @@ Re-exports will be added in model_runtime for compat.
6
6
  from __future__ import annotations
7
7
 
8
8
  import asyncio
9
- import json
10
9
  import logging
11
10
  import queue
12
11
  import subprocess
@@ -15,6 +14,8 @@ from functools import partial
15
14
  from pathlib import Path
16
15
  from typing import Any, AsyncIterator, Dict, Optional
17
16
 
17
+ from latticeai.core.sse import sse_frame
18
+
18
19
  from .model_errors import ModelRuntimeError
19
20
 
20
21
 
@@ -243,7 +244,7 @@ async def prepare_and_load_model(
243
244
 
244
245
 
245
246
  def sse_event(event: str, data: Dict[str, object]) -> str:
246
- return f"event: {event}\ndata: {json.dumps(data, ensure_ascii=False)}\n\n"
247
+ return sse_frame(event, data)
247
248
 
248
249
 
249
250
  async def prepare_and_load_model_stream(
@@ -104,81 +104,24 @@ from latticeai.services.model_runtime.engines import (
104
104
  from latticeai.services.model_runtime.engines import (
105
105
  _LOCAL_SERVER_PROCESSES as _LOCAL_SERVER_PROCESSES,
106
106
  )
107
- from latticeai.services.model_runtime.engines import (
108
- LMSTUDIO_BUNDLED_CLI as LMSTUDIO_BUNDLED_CLI,
109
- )
110
107
  from latticeai.services.model_runtime.engines import (
111
108
  LOCAL_SERVER_PROCESSES as LOCAL_SERVER_PROCESSES,
112
109
  )
113
- from latticeai.services.model_runtime.engines import (
114
- VLLM_METAL_BIN as VLLM_METAL_BIN,
115
- )
116
- from latticeai.services.model_runtime.engines import (
117
- VLLM_METAL_ENV as VLLM_METAL_ENV,
118
- )
119
- from latticeai.services.model_runtime.engines import (
120
- VLLM_METAL_PYTHON as VLLM_METAL_PYTHON,
121
- )
122
110
  from latticeai.services.model_runtime.engines import (
123
111
  _engine_install_plan as _engine_install_plan,
124
112
  )
125
- from latticeai.services.model_runtime.engines import (
126
- _engine_support_status as _engine_support_status,
127
- )
128
- from latticeai.services.model_runtime.engines import (
129
- _ensure_llamacpp_server as _ensure_llamacpp_server,
130
- )
131
- from latticeai.services.model_runtime.engines import (
132
- _ensure_lmstudio_server as _ensure_lmstudio_server,
133
- )
134
- from latticeai.services.model_runtime.engines import (
135
- _ensure_ollama_server as _ensure_ollama_server,
136
- )
137
- from latticeai.services.model_runtime.engines import (
138
- _ensure_vllm_server as _ensure_vllm_server,
139
- )
140
- from latticeai.services.model_runtime.engines import (
141
- _find_lmstudio_cli as _find_lmstudio_cli,
142
- )
143
113
  from latticeai.services.model_runtime.engines import (
144
114
  _find_lmstudio_model_key as _find_lmstudio_model_key,
145
115
  )
146
- from latticeai.services.model_runtime.engines import (
147
- _get_ollama_pulled_models as _get_ollama_pulled_models,
148
- )
149
- from latticeai.services.model_runtime.engines import (
150
- _get_openai_compatible_server_models as _get_openai_compatible_server_models,
151
- )
152
116
  from latticeai.services.model_runtime.engines import (
153
117
  _json_request as _json_request,
154
118
  )
155
119
  from latticeai.services.model_runtime.engines import (
156
120
  _lmstudio_candidate_keys as _lmstudio_candidate_keys,
157
121
  )
158
- from latticeai.services.model_runtime.engines import (
159
- _local_binary as _local_binary,
160
- )
161
- from latticeai.services.model_runtime.engines import (
162
- _pull_ollama_model_with_progress as _pull_ollama_model_with_progress,
163
- )
164
122
  from latticeai.services.model_runtime.engines import (
165
123
  _safe_engine_install_plan as _safe_engine_install_plan,
166
124
  )
167
- from latticeai.services.model_runtime.engines import (
168
- _update_env_file as _update_env_file,
169
- )
170
- from latticeai.services.model_runtime.engines import (
171
- _vllm_executable as _vllm_executable,
172
- )
173
- from latticeai.services.model_runtime.engines import (
174
- _vllm_metal_python as _vllm_metal_python,
175
- )
176
- from latticeai.services.model_runtime.engines import (
177
- _wait_for_openai_compatible_server as _wait_for_openai_compatible_server,
178
- )
179
- from latticeai.services.model_runtime.engines import (
180
- _windows_binary_candidates as _windows_binary_candidates,
181
- )
182
125
  from latticeai.services.model_runtime.engines import (
183
126
  engine_installed as engine_installed,
184
127
  )
@@ -1,10 +1,17 @@
1
1
  """Local engine discovery, server hand-off, and the LM Studio HTTP client.
2
2
 
3
- Thin wrappers over ``latticeai.services.model_engines`` that keep the historical
3
+ Re-exports from ``latticeai.services.model_engines`` that keep the historical
4
4
  ``model_runtime`` import path alive, plus the one piece of real logic that never
5
5
  belonged in the engine layer: the LM Studio native API (list / download / load)
6
6
  and its short-lived model cache.
7
7
 
8
+ Until 11.5.2 the re-exports were fifteen hand-written one-line functions that
9
+ forwarded their arguments, next to second copies of ``_json_request`` and the
10
+ LM Studio base-URL pair. A forwarding wrapper is a place two implementations
11
+ can drift — one of the copies had already grown a different fallback — so the
12
+ names are now bound directly to the engine layer's and there is nothing left to
13
+ disagree with.
14
+
8
15
  ``_LMSTUDIO_MODELS_CACHE`` and ``_LMSTUDIO_MODELS_CACHE_TS`` are rebindable
9
16
  module state and therefore live here and nowhere else — the package
10
17
  ``__init__`` deliberately does not re-export them, because a
@@ -15,7 +22,6 @@ the live value.
15
22
  from __future__ import annotations
16
23
 
17
24
  import importlib.util
18
- import json
19
25
  import os
20
26
  import shutil
21
27
  import time
@@ -24,131 +30,55 @@ import urllib.request
24
30
  from pathlib import Path
25
31
  from typing import Any, Dict, List, Optional
26
32
 
27
- from latticeai.models.router import OPENAI_COMPATIBLE_PROVIDERS, AsyncOpenAI
33
+ from latticeai.models.router import AsyncOpenAI
28
34
  from latticeai.services.model_engines import (
29
35
  LOCAL_SERVER_PROCESSES as _LOCAL_SERVER_PROCESSES,
30
36
  )
31
37
  from latticeai.services.model_engines import (
32
- engine_install_plan as _engine_install_plan,
33
- )
34
- from latticeai.services.model_engines import (
35
- engine_support_status as _engine_support_status,
36
- )
37
- from latticeai.services.model_engines import (
38
- ensure_llamacpp_server as _ensure_llamacpp_server,
38
+ _json_request as _json_request,
39
39
  )
40
40
  from latticeai.services.model_engines import (
41
- ensure_lmstudio_server as _ensure_lmstudio_server,
42
- )
43
- from latticeai.services.model_engines import (
44
- ensure_ollama_server as _ensure_ollama_server,
41
+ engine_install_plan as _engine_install_plan,
45
42
  )
46
43
  from latticeai.services.model_engines import (
47
- ensure_vllm_server as _ensure_vllm_server,
44
+ engine_support_status as engine_support_status,
48
45
  )
49
46
  from latticeai.services.model_engines import (
50
- find_lmstudio_cli as _find_lmstudio_cli,
47
+ ensure_llamacpp_server as ensure_llamacpp_server,
51
48
  )
52
49
  from latticeai.services.model_engines import (
53
- get_ollama_pulled_models as _get_ollama_pulled_models,
50
+ ensure_lmstudio_server as ensure_lmstudio_server,
54
51
  )
55
52
  from latticeai.services.model_engines import (
56
- get_openai_compatible_server_models as _get_openai_compatible_server_models,
53
+ ensure_ollama_server as ensure_ollama_server,
57
54
  )
55
+ from latticeai.services.model_engines import ensure_vllm_server as ensure_vllm_server
56
+ from latticeai.services.model_engines import find_lmstudio_cli as find_lmstudio_cli
58
57
  from latticeai.services.model_engines import (
59
- local_binary as _local_binary,
58
+ get_ollama_pulled_models as get_ollama_pulled_models,
60
59
  )
61
60
  from latticeai.services.model_engines import (
62
- pull_ollama_model_with_progress as _pull_ollama_model_with_progress,
61
+ get_openai_compatible_server_models as get_openai_compatible_server_models,
63
62
  )
63
+ from latticeai.services.model_engines import lmstudio_api_base as lmstudio_api_base
64
64
  from latticeai.services.model_engines import (
65
- vllm_executable as _vllm_executable,
65
+ lmstudio_native_api_base as lmstudio_native_api_base,
66
66
  )
67
+ from latticeai.services.model_engines import local_binary as local_binary
67
68
  from latticeai.services.model_engines import (
68
- vllm_metal_python as _vllm_metal_python,
69
+ pull_ollama_model_with_progress as pull_ollama_model_with_progress,
69
70
  )
71
+ from latticeai.services.model_engines import vllm_executable as vllm_executable
72
+ from latticeai.services.model_engines import vllm_metal_python as vllm_metal_python
70
73
  from latticeai.services.model_engines import (
71
- wait_for_openai_compatible_server as _wait_for_openai_compatible_server,
74
+ wait_for_openai_compatible_server as wait_for_openai_compatible_server,
72
75
  )
73
76
  from latticeai.services.model_engines import (
74
- windows_binary_candidates as _windows_binary_candidates,
77
+ windows_binary_candidates as windows_binary_candidates,
75
78
  )
76
79
  from latticeai.services.model_errors import ModelRuntimeError
77
80
 
78
-
79
- def _update_env_file(env_file: Path, key: str, value: str) -> None:
80
- lines = []
81
- found = False
82
- if env_file.exists():
83
- for line in env_file.read_text(encoding="utf-8").splitlines():
84
- if line.startswith(f"{key}="):
85
- lines.append(f"{key}={value}")
86
- found = True
87
- else:
88
- lines.append(line)
89
- if not found:
90
- lines.append(f"{key}={value}")
91
- env_file.write_text("\n".join(lines) + "\n", encoding="utf-8")
92
-
93
-
94
81
  LOCAL_SERVER_PROCESSES = _LOCAL_SERVER_PROCESSES
95
- VLLM_METAL_ENV = Path.home() / ".venv-vllm-metal"
96
- VLLM_METAL_BIN = VLLM_METAL_ENV / "bin" / "vllm"
97
- VLLM_METAL_PYTHON = VLLM_METAL_ENV / "bin" / "python"
98
- LMSTUDIO_BUNDLED_CLI = Path("/Applications/LM Studio.app/Contents/Resources/app/.webpack/lms")
99
-
100
- def windows_binary_candidates(binary: str) -> List[Path]:
101
- return _windows_binary_candidates(binary)
102
-
103
-
104
- def local_binary(binary: str) -> Optional[str]:
105
- return _local_binary(binary)
106
-
107
-
108
- def find_lmstudio_cli() -> Optional[str]:
109
- return _find_lmstudio_cli()
110
-
111
-
112
- def vllm_executable() -> Optional[str]:
113
- return _vllm_executable()
114
-
115
-
116
- def vllm_metal_python() -> Optional[str]:
117
- return _vllm_metal_python()
118
-
119
-
120
- def _json_request(
121
- url: str,
122
- *,
123
- method: str = "GET",
124
- payload: Optional[Dict[str, Any]] = None,
125
- headers: Optional[Dict[str, str]] = None,
126
- timeout: float = 10.0,
127
- ) -> Dict[str, Any]:
128
- data = None
129
- req_headers = dict(headers or {})
130
- if payload is not None:
131
- data = json.dumps(payload).encode("utf-8")
132
- req_headers.setdefault("Content-Type", "application/json")
133
- req = urllib.request.Request(url, data=data, headers=req_headers, method=method)
134
- with urllib.request.urlopen(req, timeout=timeout) as res:
135
- raw = res.read().decode("utf-8", errors="replace")
136
- if not raw.strip():
137
- return {}
138
- return json.loads(raw)
139
-
140
-
141
- def lmstudio_api_base() -> str:
142
- return (os.getenv("LMSTUDIO_BASE_URL") or OPENAI_COMPATIBLE_PROVIDERS["lmstudio"]["base_url"]).rstrip("/")
143
-
144
-
145
- def lmstudio_native_api_base() -> str:
146
- base = lmstudio_api_base()
147
- return base[:-3] if base.endswith("/v1") else base
148
-
149
-
150
- def ensure_lmstudio_server() -> None:
151
- return _ensure_lmstudio_server()
152
82
 
153
83
 
154
84
  _LMSTUDIO_MODELS_CACHE: List[Dict[str, Any]] = []
@@ -280,37 +210,6 @@ def ensure_lmstudio_model(model_name: str) -> Dict[str, Any]:
280
210
  "cached": False,
281
211
  }
282
212
 
283
- def engine_support_status(engine: str) -> Dict[str, Any]:
284
- return _engine_support_status(engine)
285
-
286
- def pull_ollama_model_with_progress(model_name: str, progress_emit=None) -> Dict[str, Any]:
287
- return _pull_ollama_model_with_progress(model_name, progress_emit)
288
-
289
-
290
- def get_ollama_pulled_models() -> set:
291
- return _get_ollama_pulled_models()
292
-
293
-
294
- def get_openai_compatible_server_models(provider: str) -> List[str]:
295
- return _get_openai_compatible_server_models(provider)
296
-
297
-
298
- def ensure_ollama_server() -> None:
299
- return _ensure_ollama_server()
300
-
301
-
302
- def wait_for_openai_compatible_server(provider: str, model_name: Optional[str] = None, timeout: int = 45) -> bool:
303
- return _wait_for_openai_compatible_server(provider, model_name=model_name, timeout=timeout)
304
-
305
-
306
- def ensure_vllm_server(model_name: str) -> None:
307
- return _ensure_vllm_server(model_name)
308
-
309
-
310
- def ensure_llamacpp_server(model_name: str) -> None:
311
- return _ensure_llamacpp_server(model_name)
312
-
313
-
314
213
  def _safe_engine_install_plan(
315
214
  engine: str,
316
215
  *,
@@ -9,10 +9,10 @@ load itself (blocking and streaming forms) and to
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
- import json
13
12
  from typing import Any, AsyncIterator, Dict, Optional
14
13
 
15
14
  from latticeai.core.model_resolution import ModelResolution as _ModelResolution
15
+ from latticeai.core.sse import sse_frame
16
16
  from latticeai.models.router import OPENAI_COMPATIBLE_PROVIDERS, ensure_mlx_runtime
17
17
  from latticeai.services.model_catalog import ENGINE_INSTALLERS, MODEL_ENGINE_ALIASES
18
18
  from latticeai.services.model_errors import ModelRuntimeError
@@ -151,7 +151,7 @@ async def prepare_and_load_model(
151
151
 
152
152
 
153
153
  def sse_event(event: str, data: Dict[str, Any]) -> str:
154
- return f"event: {event}\ndata: {json.dumps(data, ensure_ascii=False)}\n\n"
154
+ return sse_frame(event, data)
155
155
 
156
156
 
157
157
  async def prepare_and_load_model_stream(