Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
214a472
feat: Migrated data and config to allow overriding their paths with a…
esolithe Mar 29, 2026
9bf12d5
feat: Updated gitignore to handle the python compiling and configurat…
esolithe Mar 29, 2026
27189f7
feat: Moved webui dependancies offline
esolithe Mar 29, 2026
9f0d9a9
feat: Modified SSL verification to allow self signed certs
esolithe Mar 29, 2026
8a0fb48
feat: Moved the SSL verification flag and data directory to the confi…
esolithe Mar 29, 2026
9c550e7
merge
esolithe Apr 9, 2026
2a56407
Merge remote-tracking branch 'upstream/dev' into eso
esolithe Apr 9, 2026
ae15dae
Merge remote-tracking branch 'upstream/dev' into eso
esolithe Apr 9, 2026
78e3b05
fix: Merge
esolithe Apr 9, 2026
92996ff
Merge remote-tracking branch 'upstream/dev' into eso
esolithe Apr 9, 2026
4fb45a9
Merge remote-tracking branch 'upstream/dev' into eso
esolithe Apr 10, 2026
8e0e121
Merge remote-tracking branch 'upstream/dev' into eso
esolithe Apr 10, 2026
5497335
Merge remote-tracking branch 'upstream/dev' into eso
esolithe Apr 14, 2026
a1638a2
feat: Changed default channels to exclude CLI as it does not make sen…
esolithe Apr 14, 2026
6eb05e0
revert: Config
esolithe Apr 14, 2026
a120b93
feat: Addition of missing properties
esolithe Apr 14, 2026
06d118f
feat: Removed old config impl
esolithe Apr 14, 2026
51c5c8c
feat: Addition of config arg
esolithe Apr 14, 2026
20b80b3
feat: Disabled CLI
esolithe Apr 14, 2026
75b15c1
feat: Removed unneeded changes
esolithe Apr 14, 2026
f3f1e3b
merge: Latest
esolithe Apr 15, 2026
027877f
merge: Fixes
esolithe Apr 15, 2026
759ae80
feat: Reverted unneeded changes
esolithe Apr 15, 2026
5e10103
merge: Latest
esolithe Apr 15, 2026
275cc01
fix: Redundant code
esolithe Apr 15, 2026
cc55b7d
feat: Update to allow running within esobold (args are passed if main…
esolithe Apr 15, 2026
e54db1d
Merge remote-tracking branch 'upstream/dev' into eso
esolithe Apr 15, 2026
8ef3c0e
merge: Upstream
esolithe Apr 15, 2026
00fb980
fix: Type naming
esolithe Apr 15, 2026
fbf0f9f
fix: Redundant code
esolithe Apr 15, 2026
c04a9b5
Merge remote-tracking branch 'upstream/dev' into eso
esolithe Apr 15, 2026
a5ef229
fix: Redundant code
esolithe Apr 16, 2026
3a30c9e
fix: Redundant code
esolithe Apr 16, 2026
0524ad2
merge: Upstream
esolithe Apr 16, 2026
e044d27
Merge remote-tracking branch 'upstream/dev' into eso
esolithe Apr 16, 2026
b7857c6
Merge remote-tracking branch 'upstream/dev' into eso
esolithe Apr 17, 2026
8abc668
fix: Quotes in memory
esolithe Apr 17, 2026
69807e0
Merge remote-tracking branch 'upstream/dev' into eso
esolithe Apr 17, 2026
6a11d27
Merge remote-tracking branch 'upstream/dev' into eso
esolithe Apr 20, 2026
cac9e45
Merge remote-tracking branch 'upstream/dev' into eso
esolithe Apr 20, 2026
d75fb76
merge: Upstream
esolithe Apr 23, 2026
4c74447
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 23, 2026
16689fd
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 23, 2026
8c9df67
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 23, 2026
5c40fe6
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 24, 2026
97a4c87
merge: Upstream
esolithe Apr 25, 2026
d1001d3
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 25, 2026
4efd0c4
merge: Upstream
esolithe Apr 25, 2026
92ed474
Merge branch 'eso' of https://github.com/esolithe/opticlaw into eso
esolithe Apr 25, 2026
18d187f
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 25, 2026
af5c643
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 25, 2026
ab5e71e
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 25, 2026
3d533bf
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 25, 2026
abbb7ed
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 25, 2026
d3f079f
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 26, 2026
b6fefcf
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 26, 2026
e5a27e0
fix: Restart issue
esolithe Apr 26, 2026
dc75e8c
merge: Upstream
esolithe Apr 26, 2026
cf90743
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 29, 2026
7d50bc0
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 29, 2026
9b932ba
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 29, 2026
9b3c6df
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 29, 2026
9fb5005
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 29, 2026
2291b52
Merge remote-tracking branch 'upstream/main' into eso
esolithe Apr 30, 2026
0d94401
Update README.md
Rose22 May 1, 2026
e7ffa0a
Merge remote-tracking branch 'upstream/main' into eso
esolithe May 1, 2026
44ee922
Merge remote-tracking branch 'upstream/main' into eso
esolithe May 5, 2026
6c3ac36
Merge remote-tracking branch 'upstream/main' into eso
esolithe May 5, 2026
2c343fd
Merge remote-tracking branch 'upstream/main' into eso
esolithe May 6, 2026
22c0746
Merge remote-tracking branch 'upstream/main' into eso
esolithe May 6, 2026
0e75005
Merge remote-tracking branch 'upstream/main' into eso
esolithe May 17, 2026
366782c
fix: WS not being path aware
esolithe May 17, 2026
d504e66
Merge remote-tracking branch 'upstream/main' into eso
esolithe May 17, 2026
e6b3e92
Merge remote-tracking branch 'upstream/HEAD' into eso
esolithe May 17, 2026
1ecf410
Merge remote-tracking branch 'upstream/main' into eso
esolithe May 17, 2026
895c98e
Merge remote-tracking branch 'upstream/HEAD' into eso
esolithe May 28, 2026
10dcc6a
merge: Upstream
esolithe Jun 13, 2026
cdc27fe
fix: Absolute path of themes.js
esolithe Jun 13, 2026
e148f57
Merge remote-tracking branch 'upstream/main' into eso
esolithe Jul 2, 2026
bbde442
Merge remote-tracking branch 'upstream/main' into eso
esolithe Jul 2, 2026
9f16ac9
Merge remote-tracking branch 'upstream/main' into eso
esolithe Jul 2, 2026
ef14079
Merge remote-tracking branch 'upstream/main' into eso
esolithe Jul 2, 2026
d6527ff
fix: Fix for exit
esolithe Jul 3, 2026
e6b4f96
fix: Fix for imports when frozen
esolithe Jul 4, 2026
7e7338b
revert: Esobold specific changes
esolithe Jul 4, 2026
e0d47d7
revert: Esobold specific changes
esolithe Jul 4, 2026
586d1de
revert: Esobold specific changes
esolithe Jul 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 33 additions & 33 deletions channels/api_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,35 @@
from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any, Union


class ChatMessage(BaseModel):
role: str
content: Optional[str] = None
name: Optional[str] = None


class ChatCompletionRequest(BaseModel):
model: str
messages: List[ChatMessage]
stream: Optional[bool] = False
temperature: Optional[float] = 1.0
top_p: Optional[float] = 1.0
n: Optional[int] = 1
max_tokens: Optional[int] = None
stop: Optional[Union[str, List[str]]] = None
presence_penalty: Optional[float] = 0.0
frequency_penalty: Optional[float] = 0.0


class Model(BaseModel):
id: str
object: str = "model"


class ModelsResponse(BaseModel):
object: str = "list"
data: List[Model]

class ApiBridge(core.channel.Channel):
"""
Lets you use any application or UI (for example, koboldlite, openwebui, etc) to talk to your OpenLumara instance. Simply connect your chosen application to the port you specify in this channel's settings.
Expand Down Expand Up @@ -61,35 +90,6 @@ class ApiBridge(core.channel.Channel):
dependencies = ["fastapi", "uvicorn"]
# pydantic and httpx are already included with openlumara

# -------------------------
# MODELS (OpenAI Spec)
# -------------------------

class ChatMessage(BaseModel):
role: str
content: Optional[str] = None
name: Optional[str] = None

class ChatCompletionRequest(BaseModel):
model: str
messages: List[ChatMessage]
stream: Optional[bool] = False
temperature: Optional[float] = 1.0
top_p: Optional[float] = 1.0
n: Optional[int] = 1
max_tokens: Optional[int] = None
stop: Optional[Union[str, List[str]]] = None
presence_penalty: Optional[float] = 0.0
frequency_penalty: Optional[float] = 0.0

class Model(BaseModel):
id: str
object: str = "model"

class ModelsResponse(BaseModel):
object: str = "list"
data: List[Model]

# -------------------------
# EVENT HANDLERS
# -------------------------
Expand Down Expand Up @@ -140,15 +140,15 @@ async def auth_middleware(request: Request, call_next):
@app.get("/v1/models")
async def list_models():
"""Returns a list of available models."""
models = [self.Model(id="openlumara")]
models = [Model(id="openlumara")]
#for model_id in await self.manager.API.list_models():
# models.append(self.Model(id=model_id))
return self.ModelsResponse(data=models)
# models.append(Model(id=model_id))
return ModelsResponse(data=models)

@app.post("/v1/chat/completions")
async def chat_completions(request: Request):
body = await request.json()
chat_req = self.ChatCompletionRequest(**body)
chat_req = ChatCompletionRequest(**body)

if not chat_req.messages:
raise HTTPException(status_code=400, detail="No messages provided")
Expand Down
2 changes: 1 addition & 1 deletion channels/webui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,7 @@ <h2 id="log-title">System Logs</h2>
</div>
</div>

<script src="/themes.js"></script>
<script src="themes.js"></script>
{% for filename in js_files %}
<script src="/static/js/{{ filename }}.js"></script>
{% endfor %}
Expand Down
5 changes: 4 additions & 1 deletion core/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import json_repair
import inspect
import re
import traceback

global_instance = None

Expand Down Expand Up @@ -83,6 +84,8 @@ async def _load_channels(self, storage, channels, enabled_channels, is_user_chan
await storage[channel_name].on_install()

except Exception as e:
print(f"[CORE] failed to load channel {channel_name}: {core.detail_error(e)}", file=sys.stderr, flush=True)
traceback.print_exc()
self.log(channel_name, f"failed to load channel: {core.detail_error(e)}")

is_user_str = "user " if is_user_channels else ""
Expand Down Expand Up @@ -157,7 +160,7 @@ async def run(self):

if not enabled_channels:
print("ERROR: At least one channel must be enabled in the config! Try the `cli` channel for a basic terminal UI.", flush=True)
exit(1)
sys.exit(1)

import channels
import modules
Expand Down
143 changes: 131 additions & 12 deletions core/modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@
import sys
import subprocess
import ast
import traceback
import importlib.util
from packaging.requirements import Requirement, InvalidRequirement

try:
from importlib.metadata import version, PackageNotFoundError
from importlib.metadata import version, PackageNotFoundError, packages_distributions
except ImportError:
from importlib_metadata import version, PackageNotFoundError
from importlib_metadata import version, PackageNotFoundError, packages_distributions

# modules that should have their prompts inserted even when tools are off
nonagentic = ("characters", "writing_style", "time")

reported_missing = []
reported_broken = []
reported_missing_console = set()
dist_to_import_cache = None

# buffer the warnings and errors so that we can propagate them to manager.log()
log_buffer = []
Expand Down Expand Up @@ -88,15 +93,121 @@ def _get_module_file_path(package, module_name):
return spec.origin
return None

def _normalize_dist_name(name: str) -> str:
return re.sub(r"[-_.]+", "-", name).lower()

def _build_dist_to_import_map():
"""Build a mapping of distribution names to top-level import names."""
global dist_to_import_cache
if dist_to_import_cache is not None:
return dist_to_import_cache

dist_to_import_cache = {}
try:
pkg_to_dist = packages_distributions() or {}
for import_name, dists in pkg_to_dist.items():
for dist_name in dists or []:
key = _normalize_dist_name(dist_name)
dist_to_import_cache.setdefault(key, set()).add(import_name)
except Exception:
pass

return dist_to_import_cache

def _parse_requirement(dep: str):
"""Parse a requirement string, returning None when it cannot be parsed."""
try:
return Requirement(dep)
except InvalidRequirement:
# Keep compatibility for loose inputs by extracting a likely package token.
token = dep.split(";", 1)[0].strip()
for op in ("===", "==", ">=", "<=", "~=", "!=", ">", "<"):
if op in token:
token = token.split(op, 1)[0].strip()
break
token = token.split("[", 1)[0].strip()
if not token:
return None
try:
return Requirement(token)
except InvalidRequirement:
return None

def _candidate_import_names(dist_name: str):
"""Generate import-name candidates from a distribution name generically."""
raw = dist_name.strip()
candidates = set()

if not raw:
return candidates

# Canonical python import-style transform.
candidates.add(raw.replace("-", "_").replace(".", "_"))

# Tokenized variants cover common pip-name vs import-name drift.
tokens = [t for t in re.split(r"[-_.]+", raw) if t]
if tokens:
candidates.add(tokens[0])
candidates.add(tokens[-1])

# Common wheel naming convention: python-foo-bar -> foo/bar/foo_bar
if raw.startswith("python-") and len(tokens) > 1:
tail_tokens = tokens[1:]
candidates.add("_".join(tail_tokens))
candidates.add(tail_tokens[0])
candidates.add(tail_tokens[-1])

# Distribution metadata mapping (when available) is most accurate.
for import_name in _build_dist_to_import_map().get(_normalize_dist_name(raw), set()):
candidates.add(import_name)

return {c for c in candidates if c and c not in {"python", "py"}}

def _check_missing_deps(deps):
"""return list of dependencies that are not installed (using pip package names)"""
missing = []
for dep in deps:
# extract the base package name (e.g. 'python-telegram-bot' from 'python-telegram-bot>=1.0')
pkg_name = dep.split('>=')[0].split('==')[0].split('<')[0].split('>')[0].strip()
"""Return dependencies that are unavailable in this runtime.

This uses requirement parsing and generic import discovery instead of
hardcoded package-name mappings.
"""

def _is_dep_available(dep: str) -> bool:
req = _parse_requirement(dep)
if not req:
return False

# Ignore requirements gated off by environment markers.
if req.marker is not None:
try:
if not req.marker.evaluate():
return True
except Exception:
pass

dist_name = req.name
dist_key = _normalize_dist_name(dist_name)

# First try metadata lookup (best for non-frozen installs).
try:
version(pkg_name)
version(dist_name)
return True
except PackageNotFoundError:
pass

# Generic fallback: infer likely import names and test importability.
candidates = _candidate_import_names(dist_name)

for import_name in candidates:
try:
if importlib.util.find_spec(import_name) is not None:
return True
except Exception:
continue

return False

missing = []
for dep in deps:
if not _is_dep_available(dep):
missing.append(dep)
return missing

Expand Down Expand Up @@ -246,11 +357,17 @@ def load(package, base_class = None, filter: list = None, reload: bool = False,
if deps:
missing = _check_missing_deps(deps)
if missing:
if modname not in reported_missing and not loading_config:
log(modname, "Warning: loading skipped because of missing dependencies")
reported_missing.append(modname)
if modname not in reported_missing_console:
print(f"[CORE] skipping {modname} because of missing dependencies: {', '.join(missing)}", file=sys.stderr, flush=True)
reported_missing_console.add(modname)
# In frozen/PyInstaller builds, metadata lookups can be incomplete.
# Do not hard-skip here: attempt import and let real import errors surface.
if not getattr(sys, "frozen", False):
if modname not in reported_missing and not loading_config:
log(modname, "Warning: loading skipped because of missing dependencies")
reported_missing.append(modname)

continue
continue

try:
# Import the module relative to the package
Expand Down Expand Up @@ -291,6 +408,8 @@ def load(package, base_class = None, filter: list = None, reload: bool = False,
if modname in reported_broken:
continue

print(f"[CORE] failed to load module {modname}: {core.detail_error(e)}", file=sys.stderr, flush=True)
traceback.print_exc()
log("core", f"failed to load module {modname}: {core.detail_error(e)}")
reported_broken.append(modname)
continue
Expand Down
3 changes: 2 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import core
import subprocess
import argparse
import sys

async def main_loop(arg_list):
# parse the --config arg
Expand Down Expand Up @@ -94,7 +95,7 @@ def run_from_args(arg_list: list = []):
print("-" * 40, flush=True)
pass
else:
exit()
sys.exit(0)

def str2bool(v):
if isinstance(v, bool):
Expand Down