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.
- package/README.md +86 -47
- package/docs/CHANGELOG.md +76 -0
- package/docs/COMMUNITY_AND_PLUGINS.md +1 -1
- package/docs/DEVELOPMENT.md +1 -1
- package/docs/HYBRID_CLOUD_KG_STREAMING.md +8 -4
- package/docs/MULTI_AGENT_RUNTIME.md +1 -1
- package/docs/ONBOARDING.md +1 -1
- package/docs/OPERATIONS.md +1 -1
- package/docs/TRUST_MODEL.md +1 -1
- package/docs/WHY_LATTICE.md +1 -1
- package/docs/WORKFLOW_DESIGNER.md +1 -1
- package/docs/architecture.md +4 -4
- package/docs/kg-schema.md +1 -1
- package/docs/v11.5.1_RUST_FULL_LOOP_PLAN.md +85 -0
- package/docs/v11.5.2_TIGHT_SHIP_PLAN.md +144 -0
- package/lattice_brain/__init__.py +1 -1
- package/lattice_brain/graph/proactive.py +1 -1
- package/lattice_brain/graph/projection/v2_schema.py +0 -28
- package/lattice_brain/graph/vector_index/base.py +0 -3
- package/lattice_brain/graph/vector_index/brute_force.py +0 -4
- package/lattice_brain/graph/vector_index/hnsw.py +0 -3
- package/lattice_brain/graph/vector_index/quantized.py +0 -3
- package/lattice_brain/ingestion_jobs.py +0 -3
- package/lattice_brain/memory.py +0 -27
- package/lattice_brain/portability/sharing.py +0 -4
- package/lattice_brain/quality.py +0 -49
- package/lattice_brain/runtime/agent_runtime.py +1 -1
- package/lattice_brain/runtime/hooks.py +0 -13
- package/lattice_brain/runtime/multi_agent.py +1 -1
- package/lattice_brain/sealed_box.py +0 -4
- package/latticeai/__init__.py +1 -1
- package/latticeai/api/admin.py +12 -12
- package/latticeai/api/agent_worker_seam.py +389 -0
- package/latticeai/api/auth.py +25 -2
- package/latticeai/api/chat.py +3 -3
- package/latticeai/api/chat_agent_http.py +3 -12
- package/latticeai/api/chat_helpers.py +10 -13
- package/latticeai/api/computer_use.py +31 -42
- package/latticeai/api/health.py +21 -1
- package/latticeai/api/local_files.py +14 -0
- package/latticeai/api/permissions.py +38 -12
- package/latticeai/api/search.py +24 -0
- package/latticeai/api/static_routes.py +4 -1
- package/latticeai/cli/runtime.py +7 -3
- package/latticeai/core/config.py +47 -3
- package/latticeai/core/csrf.py +28 -2
- package/latticeai/core/embedding_providers/__init__.py +3 -5
- package/latticeai/core/embedding_providers/base.py +1 -1
- package/latticeai/core/embedding_providers/text.py +1 -1
- package/latticeai/core/enterprise.py +1 -4
- package/latticeai/core/http_origin.py +146 -0
- package/latticeai/core/invitations.py +3 -2
- package/latticeai/core/io_utils.py +2 -11
- package/latticeai/core/legacy_compatibility.py +1 -1
- package/latticeai/core/marketplace.py +1 -1
- package/latticeai/core/messages.py +40 -0
- package/latticeai/core/model_compat.py +2 -8
- package/latticeai/core/module_probe.py +37 -0
- package/latticeai/core/run_explain.py +5 -2
- package/latticeai/core/run_store.py +2 -2
- package/latticeai/core/security.py +13 -0
- package/latticeai/core/sessions.py +3 -2
- package/latticeai/core/sse.py +25 -0
- package/latticeai/core/users.py +0 -9
- package/latticeai/core/workspace_graph_trace.py +2 -8
- package/latticeai/core/workspace_os.py +0 -10
- package/latticeai/core/workspace_os_constants.py +1 -1
- package/latticeai/core/workspace_os_utils.py +34 -0
- package/latticeai/core/workspace_review_items.py +2 -8
- package/latticeai/core/workspace_runs.py +2 -8
- package/latticeai/core/workspace_skills.py +2 -7
- package/latticeai/models/router/documents.py +0 -35
- package/latticeai/models/router/registry.py +0 -4
- package/latticeai/runtime/access_runtime.py +16 -4
- package/latticeai/runtime/build_phases/features.py +25 -2
- package/latticeai/runtime/build_phases/web.py +2 -0
- package/latticeai/runtime/feature_toggle_wiring.py +12 -18
- package/latticeai/runtime/network_boundary_wiring.py +8 -19
- package/latticeai/runtime/permission_mode_wiring.py +6 -13
- package/latticeai/runtime/router_registration.py +4 -0
- package/latticeai/runtime/runtime_context.py +1 -13
- package/latticeai/runtime/service_singletons.py +55 -0
- package/latticeai/services/architecture_readiness.py +1 -1
- package/latticeai/services/brain_intelligence/proposals.py +0 -5
- package/latticeai/services/change_proposals.py +2 -36
- package/latticeai/services/chronicle.py +4 -6
- package/latticeai/services/command_center.py +4 -4
- package/latticeai/services/evidence_actions.py +5 -2
- package/latticeai/services/hybrid_chat.py +2 -2
- package/latticeai/services/hybrid_policy.py +8 -57
- package/latticeai/services/mode_store.py +132 -0
- package/latticeai/services/model_capability_registry.py +0 -15
- package/latticeai/services/model_catalog.py +0 -7
- package/latticeai/services/model_loading.py +3 -2
- package/latticeai/services/model_runtime/__init__.py +0 -57
- package/latticeai/services/model_runtime/engines.py +27 -128
- package/latticeai/services/model_runtime/loading.py +2 -2
- package/latticeai/services/network_boundary_service.py +7 -44
- package/latticeai/services/permission_mode_service.py +8 -56
- package/latticeai/services/product_readiness.py +1 -1
- package/latticeai/services/setup_detection.py +67 -1
- package/latticeai/services/tool_dispatch.py +0 -4
- package/latticeai/services/upload_service.py +7 -20
- package/latticeai/services/workspace_service.py +0 -6
- package/latticeai/setup/auto_setup.py +15 -26
- package/latticeai/setup/wizard/catalog.py +5 -2
- package/latticeai/setup/wizard/detect.py +6 -27
- package/latticeai/setup/wizard/paths.py +2 -5
- package/latticeai/setup/wizard/plans.py +2 -2
- package/package.json +2 -4
- package/scripts/bump_version.py +14 -1
- package/scripts/check_current_release_docs.mjs +1 -1
- package/scripts/check_legacy_debt.mjs +1 -1
- package/scripts/check_server_i18n.mjs +1 -0
- package/scripts/generate_agent_loop_fixtures.py +994 -0
- package/scripts/generate_rust_parity_fixtures.py +72 -161
- package/scripts/parity_fixture_corpus_context.py +162 -0
- package/scripts/parity_fixture_corpus_docgen.py +341 -0
- package/scripts/release_screen_claims.json +20 -0
- package/src-tauri/Cargo.lock +13 -7
- package/src-tauri/Cargo.toml +1 -1
- package/src-tauri/src/backend.rs +76 -2
- package/src-tauri/src/main.rs +13 -3
- package/src-tauri/tauri.conf.json +2 -2
- package/static/app/asset-manifest.json +41 -41
- package/static/app/assets/{Act-CWnxSCgN.js → Act-DcQizkl1.js} +1 -1
- package/static/app/assets/{AdminConsole-BEQYU6kF.js → AdminConsole-cf4npybT.js} +1 -1
- package/static/app/assets/{Brain-DWu1BhFg.js → Brain-3VCSHFcn.js} +1 -1
- package/static/app/assets/{BrainHome-95Hilr9R.js → BrainHome-Qm8eaztx.js} +1 -1
- package/static/app/assets/{BrainSignals-QdeqCpAF.js → BrainSignals-DS9BtKOW.js} +1 -1
- package/static/app/assets/{Capture-BHpCxnzb.js → Capture-DiQ219jW.js} +1 -1
- package/static/app/assets/{Chronicle-B4xYKoed.js → Chronicle-BGvuAchH.js} +1 -1
- package/static/app/assets/{CommandPalette-BVXnttSz.js → CommandPalette-Bqhm0Urn.js} +1 -1
- package/static/app/assets/{Library-DgYcHome.js → Library-BV6NnF0a.js} +1 -1
- package/static/app/assets/{LivingBrain-CrJLDbf7.js → LivingBrain-GzenJchP.js} +1 -1
- package/static/app/assets/{ProductFlow-DFlScKoJ.js → ProductFlow-DEP6-vML.js} +1 -1
- package/static/app/assets/{ReviewCard-Cy5f48Pj.js → ReviewCard-CNZ7XjWG.js} +1 -1
- package/static/app/assets/{System-NF8IfhTa.js → System-CieofHQa.js} +1 -1
- package/static/app/assets/arrow-left-kfsrk0mv.js +1 -0
- package/static/app/assets/{bot-CucuhLhm.js → bot-B_K1Tdmw.js} +1 -1
- package/static/app/assets/{brain-BBnSryW_.js → brain-DWyaV1L1.js} +1 -1
- package/static/app/assets/{button-C2GUj2Ai.js → button-aTn4s84A.js} +1 -1
- package/static/app/assets/circle-check-qqLug9nU.js +1 -0
- package/static/app/assets/{circle-pause-CbkWzBmG.js → circle-pause-xKgeGXkT.js} +1 -1
- package/static/app/assets/{circle-play-7lEaqHdJ.js → circle-play-DkT6tYPX.js} +1 -1
- package/static/app/assets/{cpu-DAlCXlIy.js → cpu-85xYObUC.js} +1 -1
- package/static/app/assets/{download-RNhuuJwh.js → download-B5Fm7YXo.js} +1 -1
- package/static/app/assets/{folder-open-CLW4odzM.js → folder-open-kk2Xa52u.js} +1 -1
- package/static/app/assets/{hard-drive-NKEiDIAJ.js → hard-drive-DkA3zBW_.js} +1 -1
- package/static/app/assets/{index-DMurvUuR.js → index-BMPdTmlY.js} +3 -3
- package/static/app/assets/index-DxmOfNRi.css +2 -0
- package/static/app/assets/{input-D2UhPC1X.js → input-B0nRf2jO.js} +1 -1
- package/static/app/assets/{link-2-6amKbP_P.js → link-2-Dwb4gnTc.js} +1 -1
- package/static/app/assets/{permissionCopy-Cu9TZtdR.js → permissionCopy-CQDUBrOZ.js} +1 -1
- package/static/app/assets/{primitives-gPsccucr.js → primitives-SNp0LRJz.js} +1 -1
- package/static/app/assets/search-BcHqkjoy.js +1 -0
- package/static/app/assets/{share-2-Bau7KkPq.js → share-2-BsrxFglO.js} +1 -1
- package/static/app/assets/{shield-alert-BufNYypi.js → shield-alert-5BStfp2_.js} +1 -1
- package/static/app/assets/{textarea-BQnVWhYs.js → textarea-Cg8IUA-k.js} +1 -1
- package/static/app/assets/{useFocusTrap-B3_w60si.js → useFocusTrap-CYKvE46M.js} +1 -1
- package/static/app/assets/{useMutation-BHhCflT6.js → useMutation-CSn9t1op.js} +1 -1
- package/static/app/assets/{useQuery-rBWfI-5t.js → useQuery-CY2OI2uy.js} +1 -1
- package/static/app/assets/{utils-V_5-wxr5.js → utils-Ddol2RWD.js} +1 -1
- package/static/app/assets/{workspace-K1zjYUHj.js → workspace-BqDwOz_p.js} +1 -1
- package/static/app/index.html +4 -4
- package/static/sw.js +1 -1
- package/desktop/electron/README.md +0 -9
- package/desktop/electron/main.cjs +0 -58
- package/desktop/electron/preload.cjs +0 -5
- package/latticeai/core/graph_curator.py +0 -11
- package/latticeai/core/hooks.py +0 -11
- package/latticeai/core/local_embeddings.py +0 -104
- package/latticeai/core/multi_agent.py +0 -11
- package/latticeai/core/workflow_engine.py +0 -11
- package/latticeai/services/ingestion.py +0 -11
- package/latticeai/services/kg_portability.py +0 -11
- package/latticeai/services/multimodal_streaming.py +0 -129
- package/scripts/measure_brain_home_fill.mjs +0 -141
- package/static/app/assets/arrow-left-DwkSYrjR.js +0 -1
- package/static/app/assets/circle-check-CxOVPwYq.js +0 -1
- package/static/app/assets/index-BLPb5lmE.css +0 -2
- 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
|
|
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
|
-
|
|
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
|
|
60
|
-
|
|
61
|
-
self._audit = audit
|
|
44
|
+
def _default_entry(self) -> Any:
|
|
45
|
+
return _default_policy()
|
|
62
46
|
|
|
63
|
-
def
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
44
|
+
engine_support_status as engine_support_status,
|
|
48
45
|
)
|
|
49
46
|
from latticeai.services.model_engines import (
|
|
50
|
-
|
|
47
|
+
ensure_llamacpp_server as ensure_llamacpp_server,
|
|
51
48
|
)
|
|
52
49
|
from latticeai.services.model_engines import (
|
|
53
|
-
|
|
50
|
+
ensure_lmstudio_server as ensure_lmstudio_server,
|
|
54
51
|
)
|
|
55
52
|
from latticeai.services.model_engines import (
|
|
56
|
-
|
|
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
|
-
|
|
58
|
+
get_ollama_pulled_models as get_ollama_pulled_models,
|
|
60
59
|
)
|
|
61
60
|
from latticeai.services.model_engines import (
|
|
62
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
154
|
+
return sse_frame(event, data)
|
|
155
155
|
|
|
156
156
|
|
|
157
157
|
async def prepare_and_load_model_stream(
|