omnimux 0.1.0__tar.gz
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.
- omnimux-0.1.0/.github/workflows/ci.yml +38 -0
- omnimux-0.1.0/.gitignore +52 -0
- omnimux-0.1.0/.pre-commit-config.yaml +21 -0
- omnimux-0.1.0/.python-version +1 -0
- omnimux-0.1.0/Makefile +15 -0
- omnimux-0.1.0/PKG-INFO +169 -0
- omnimux-0.1.0/README.md +154 -0
- omnimux-0.1.0/assets/omnimux-banner.png +0 -0
- omnimux-0.1.0/example.models.json +83 -0
- omnimux-0.1.0/install_service.sh +79 -0
- omnimux-0.1.0/pyproject.toml +69 -0
- omnimux-0.1.0/src/omnimux/__init__.py +0 -0
- omnimux-0.1.0/src/omnimux/__main__.py +211 -0
- omnimux-0.1.0/src/omnimux/app.py +101 -0
- omnimux-0.1.0/src/omnimux/config.py +16 -0
- omnimux-0.1.0/src/omnimux/exceptions.py +25 -0
- omnimux-0.1.0/src/omnimux/models/__init__.py +0 -0
- omnimux-0.1.0/src/omnimux/models/schemas.py +123 -0
- omnimux-0.1.0/src/omnimux/py.typed +1 -0
- omnimux-0.1.0/src/omnimux/routes/__init__.py +0 -0
- omnimux-0.1.0/src/omnimux/routes/proxy.py +313 -0
- omnimux-0.1.0/src/omnimux/services/__init__.py +0 -0
- omnimux-0.1.0/src/omnimux/services/adapters.py +166 -0
- omnimux-0.1.0/src/omnimux/services/cache.py +78 -0
- omnimux-0.1.0/src/omnimux/services/compressor.py +166 -0
- omnimux-0.1.0/src/omnimux/services/db.py +324 -0
- omnimux-0.1.0/src/omnimux/services/gatekeeper.py +32 -0
- omnimux-0.1.0/src/omnimux/services/gemini_client.py +138 -0
- omnimux-0.1.0/src/omnimux/services/model_discovery.py +153 -0
- omnimux-0.1.0/src/omnimux/services/multiplexer.py +212 -0
- omnimux-0.1.0/src/omnimux/services/router_core.py +202 -0
- omnimux-0.1.0/src/omnimux/services/sync.py +48 -0
- omnimux-0.1.0/src/omnimux/services/unloader.py +68 -0
- omnimux-0.1.0/src/omnimux/utils/__init__.py +0 -0
- omnimux-0.1.0/src/omnimux/utils/cli_checklist.py +76 -0
- omnimux-0.1.0/src/omnimux/utils/html.py +29 -0
- omnimux-0.1.0/tests/__init__.py +0 -0
- omnimux-0.1.0/tests/conftest.py +21 -0
- omnimux-0.1.0/tests/e2e/test_multi_turn.py +142 -0
- omnimux-0.1.0/tests/unit/test_adapters.py +305 -0
- omnimux-0.1.0/tests/unit/test_app.py +427 -0
- omnimux-0.1.0/tests/unit/test_benchmark.py +74 -0
- omnimux-0.1.0/tests/unit/test_cache.py +160 -0
- omnimux-0.1.0/tests/unit/test_cli_checklist.py +92 -0
- omnimux-0.1.0/tests/unit/test_compressor.py +219 -0
- omnimux-0.1.0/tests/unit/test_config.py +15 -0
- omnimux-0.1.0/tests/unit/test_db.py +182 -0
- omnimux-0.1.0/tests/unit/test_exceptions.py +20 -0
- omnimux-0.1.0/tests/unit/test_gatekeeper.py +26 -0
- omnimux-0.1.0/tests/unit/test_gemini_client.py +235 -0
- omnimux-0.1.0/tests/unit/test_html.py +52 -0
- omnimux-0.1.0/tests/unit/test_main.py +281 -0
- omnimux-0.1.0/tests/unit/test_model_discovery.py +187 -0
- omnimux-0.1.0/tests/unit/test_multiplexer.py +336 -0
- omnimux-0.1.0/tests/unit/test_router_core.py +203 -0
- omnimux-0.1.0/tests/unit/test_schemas.py +76 -0
- omnimux-0.1.0/tests/unit/test_sync.py +112 -0
- omnimux-0.1.0/tests/unit/test_unloader.py +108 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Install uv
|
|
15
|
+
uses: astral-sh/setup-uv@v5
|
|
16
|
+
with:
|
|
17
|
+
enable-cache: true
|
|
18
|
+
cache-dependency-glob: "uv.lock"
|
|
19
|
+
|
|
20
|
+
- name: Setup Python
|
|
21
|
+
uses: actions/setup-python@v5
|
|
22
|
+
with:
|
|
23
|
+
python-version-file: "pyproject.toml"
|
|
24
|
+
|
|
25
|
+
- name: Install dependencies
|
|
26
|
+
run: uv sync --all-groups
|
|
27
|
+
|
|
28
|
+
- name: Lint (Ruff Format)
|
|
29
|
+
run: uv run ruff format --check .
|
|
30
|
+
|
|
31
|
+
- name: Lint (Ruff Check)
|
|
32
|
+
run: uv run ruff check .
|
|
33
|
+
|
|
34
|
+
- name: Type Check (Mypy)
|
|
35
|
+
run: uv run mypy .
|
|
36
|
+
|
|
37
|
+
- name: Test (Pytest with Coverage)
|
|
38
|
+
run: uv run pytest tests/ --cov=src/omnimux
|
omnimux-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Agent
|
|
2
|
+
.agent/
|
|
3
|
+
|
|
4
|
+
# Documentation (ERD, Testing, Plan)
|
|
5
|
+
.docs/
|
|
6
|
+
|
|
7
|
+
# Cache
|
|
8
|
+
.ruff_cache/
|
|
9
|
+
.mypy_cache/
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
|
|
12
|
+
# Python
|
|
13
|
+
__pycache__/
|
|
14
|
+
*.py[cod]
|
|
15
|
+
*$py.class
|
|
16
|
+
.coverage
|
|
17
|
+
htmlcov/
|
|
18
|
+
.venv/
|
|
19
|
+
env/
|
|
20
|
+
venv/
|
|
21
|
+
.env
|
|
22
|
+
|
|
23
|
+
# uv
|
|
24
|
+
uv.lock
|
|
25
|
+
|
|
26
|
+
# IDEs
|
|
27
|
+
.vscode/
|
|
28
|
+
.idea/
|
|
29
|
+
.cursor/
|
|
30
|
+
.logs/
|
|
31
|
+
|
|
32
|
+
# Keys
|
|
33
|
+
models.json
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Local DB
|
|
37
|
+
nexus_llm_state.db
|
|
38
|
+
omnimux_state.db
|
|
39
|
+
|
|
40
|
+
# Tests
|
|
41
|
+
e2e_out.log
|
|
42
|
+
|
|
43
|
+
# Logs
|
|
44
|
+
nexus-llm.err.log
|
|
45
|
+
nexus-llm.log
|
|
46
|
+
nexus.log
|
|
47
|
+
omnimux.log
|
|
48
|
+
omnimux.err.log
|
|
49
|
+
|
|
50
|
+
# PID files
|
|
51
|
+
.nexus.pid
|
|
52
|
+
.omnimux.pid
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: local
|
|
3
|
+
hooks:
|
|
4
|
+
- id: ruff-format
|
|
5
|
+
name: ruff format
|
|
6
|
+
entry: uv run ruff format .
|
|
7
|
+
language: system
|
|
8
|
+
types: [python]
|
|
9
|
+
pass_filenames: false
|
|
10
|
+
- id: ruff-check
|
|
11
|
+
name: ruff check
|
|
12
|
+
entry: uv run ruff check . --fix
|
|
13
|
+
language: system
|
|
14
|
+
types: [python]
|
|
15
|
+
pass_filenames: false
|
|
16
|
+
- id: mypy
|
|
17
|
+
name: mypy
|
|
18
|
+
entry: uv run mypy .
|
|
19
|
+
language: system
|
|
20
|
+
types: [python]
|
|
21
|
+
pass_filenames: false
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
omnimux-0.1.0/Makefile
ADDED
omnimux-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: omnimux
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local intelligent LLM multiplexer, proxy, and router
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: aiosqlite>=0.20.0
|
|
7
|
+
Requires-Dist: beautifulsoup4>=4.14.3
|
|
8
|
+
Requires-Dist: fastapi>=0.136.3
|
|
9
|
+
Requires-Dist: httpx>=0.28.1
|
|
10
|
+
Requires-Dist: markdownify>=1.2.2
|
|
11
|
+
Requires-Dist: pydantic-settings>=2.14.1
|
|
12
|
+
Requires-Dist: python-dotenv>=1.2.2
|
|
13
|
+
Requires-Dist: uvicorn>=0.48.0
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
<p align="center">
|
|
17
|
+
<img src="assets/omnimux-banner.png" alt="omnimux banner" width="100%">
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<strong>The adaptive local multiplexer and fallback gateway for LLM coding agents.</strong><br>
|
|
22
|
+
One endpoint. Any agent. Zero downtime.
|
|
23
|
+
</p>
|
|
24
|
+
|
|
25
|
+
<p align="center">
|
|
26
|
+
<a href="https://pypi.org/project/omnimux/"><img src="https://img.shields.io/badge/pypi-omnimux-blue.svg?style=flat-square" alt="PyPI"></a>
|
|
27
|
+
<a href="https://python.org"><img src="https://img.shields.io/badge/python-3.12%2B-blue.svg?style=flat-square" alt="Python 3.12+"></a>
|
|
28
|
+
<a href="https://github.com/lucikritz/nexus-llm/actions"><img src="https://img.shields.io/badge/coverage-100%25-brightgreen.svg?style=flat-square" alt="Coverage"></a>
|
|
29
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg?style=flat-square" alt="License"></a>
|
|
30
|
+
</p>
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## What is omnimux?
|
|
35
|
+
|
|
36
|
+
When coding agents (Claude Code, Cursor, RooCode, Continue, Open WebUI, Aider) hit rate limits (`429`), credit exhaustion (`402`), or local VRAM out-of-memory errors, tasks break mid-refactor.
|
|
37
|
+
|
|
38
|
+
**omnimux** is a lightweight, database-first local gateway that sits between your coding tools and your LLM backends. It translates between OpenAI and Anthropic protocols, multiplexes API keys across providers, automatically hot-swaps to fallbacks upon failure, and unloads idle local models before loading new ones.
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
Coding Agents (Claude Code, Cursor, RooCode, Continue, Aider)
|
|
42
|
+
│
|
|
43
|
+
[ omnimux gateway ]
|
|
44
|
+
│
|
|
45
|
+
┌───────────────┬──────────┴────────┬─────────────┬──────────┐
|
|
46
|
+
▼ ▼ ▼ ▼ ▼
|
|
47
|
+
Anthropic OpenRouter Gemini Groq Ollama
|
|
48
|
+
(Claude 3.5) (Failover pool) (Long context) (Fast) (Local zero-OOM)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Compatible Extensions & Agents
|
|
54
|
+
|
|
55
|
+
omnimux exposes standard OpenAI (`/v1/chat/completions`) and Anthropic (`/v1/messages`) endpoints, making it a drop-in replacement across tools:
|
|
56
|
+
|
|
57
|
+
| Agent / Tool | Protocol | Configuration |
|
|
58
|
+
| :--- | :--- | :--- |
|
|
59
|
+
| **Claude Code** | Native Anthropic (`/v1/messages`) | `export ANTHROPIC_BASE_URL="http://localhost:11444"`<br>`export ANTHROPIC_API_KEY="om_your_client_token"` |
|
|
60
|
+
| **Cursor** | OpenAI Compatible (`/v1`) | Base URL: `http://localhost:11444/v1`<br>API Key: `om_your_client_token` |
|
|
61
|
+
| **RooCode / Cline** | OpenAI / Anthropic format | Base URL: `http://localhost:11444/v1` or Anthropic Base URL |
|
|
62
|
+
| **Continue.dev** | OpenAI / Anthropic format | `apiBase: "http://localhost:11444/v1"` in `config.yaml` |
|
|
63
|
+
| **Open WebUI** | OpenAI Compatible (`/v1`) | Connections → OpenAI API: `http://localhost:11444/v1` |
|
|
64
|
+
| **Aider** | OpenAI Compatible (`/v1`) | `export OPENAI_API_BASE="http://localhost:11444/v1"`<br>`export OPENAI_API_KEY="om_your_client_token"` |
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Core Features
|
|
69
|
+
|
|
70
|
+
- 🔄 **Autonomous 429 Hot-Swapping**: Automatically cycles through backup API keys and provider fallbacks with intelligent backoff cooldowns when an upstream service rate-limits or fails.
|
|
71
|
+
- 🦙 **Zero-OOM Ollama Engine**: Issues `keep_alive: 0` unloads for idle local models prior to spinning up fresh models, preventing out-of-memory crashes on unified-memory Apple Silicon and CUDA GPUs.
|
|
72
|
+
- ⚡ **Dual Protocol Translation**: Complete bidirectional translation between Anthropic `/v1/messages` and OpenAI `/v1/chat/completions` with full SSE streaming and token count endpoints.
|
|
73
|
+
- 🗜️ **Context Optimizer**: Converts bulky HTML pages from agent web searches into lean markdown and deduplicates multimodal vision images with local SHA-256 caching.
|
|
74
|
+
- 🔐 **Scoped Client Keys**: Generate isolated client tokens (`om_...`) per IDE extension with local database tracking, usage timestamps, and instant revocation.
|
|
75
|
+
- 📦 **Daemon & Single-Binary Simplicity**: Run seamlessly as a background service via `omnimux start -d` or inspect active logs with `omnimux status`.
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Quickstart
|
|
80
|
+
|
|
81
|
+
### 1. Installation
|
|
82
|
+
|
|
83
|
+
Run via `uvx` (no installation required) or install with `pip`/`uv`:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Run immediately via uvx
|
|
87
|
+
uvx omnimux --help
|
|
88
|
+
|
|
89
|
+
# Or install globally into your environment
|
|
90
|
+
uv pip install omnimux
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
*(Note: `omni`, `nexus`, and `nexus-llm` are registered aliases for all CLI commands.)*
|
|
94
|
+
|
|
95
|
+
### 2. Discover & Register Models
|
|
96
|
+
|
|
97
|
+
Interactively discover and register models from local Ollama or upstream providers:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
omnimux model add
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 3. Start the Gateway
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Start in the foreground
|
|
107
|
+
omnimux start --port 11444
|
|
108
|
+
|
|
109
|
+
# Or start as a background daemon
|
|
110
|
+
omnimux start -d --port 11444
|
|
111
|
+
|
|
112
|
+
# Check status or stop
|
|
113
|
+
omnimux status
|
|
114
|
+
omnimux stop
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 4. Create an Agent Token
|
|
118
|
+
|
|
119
|
+
Create an isolated client key for your coding agent:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
omnimux keys create claude-code
|
|
123
|
+
# Output: om_k9A2x...
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Then point your agent:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
export ANTHROPIC_BASE_URL="http://localhost:11444"
|
|
130
|
+
export ANTHROPIC_API_KEY="om_k9A2x..."
|
|
131
|
+
claude
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## CLI Reference
|
|
137
|
+
|
|
138
|
+
| Command | Description |
|
|
139
|
+
| :--- | :--- |
|
|
140
|
+
| `omnimux start [--port PORT] [-d]` | Start the gateway server (foreground or daemon) |
|
|
141
|
+
| `omnimux stop` | Terminate running daemon process |
|
|
142
|
+
| `omnimux status` | Inspect health, PID, and port of running daemon |
|
|
143
|
+
| `omnimux model add` | Interactively query and register upstream models |
|
|
144
|
+
| `omnimux model list` | List registered platform models and endpoints |
|
|
145
|
+
| `omnimux keys create [name]` | Generate a client API token (`om_...`) |
|
|
146
|
+
| `omnimux keys list` | List active client keys and last used timestamps |
|
|
147
|
+
| `omnimux keys revoke <id>` | Revoke a client key |
|
|
148
|
+
| `omnimux export <file.json>` | Export entire SQLite routing state to JSON |
|
|
149
|
+
| `omnimux cache --clear` | Purge local image and request cache |
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Configuration
|
|
154
|
+
|
|
155
|
+
Settings can be overridden via environment variables or a local `.env` file:
|
|
156
|
+
|
|
157
|
+
```env
|
|
158
|
+
PORT=11444
|
|
159
|
+
SQLITE_DB_PATH=omnimux_state.db
|
|
160
|
+
PROXY_PASSWORD=optional_fallback_password
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Legacy `nx_...` keys and existing SQLite states are automatically recognized and supported for backwards compatibility.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## License
|
|
168
|
+
|
|
169
|
+
MIT © LuciKritZ
|
omnimux-0.1.0/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/omnimux-banner.png" alt="omnimux banner" width="100%">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center">
|
|
6
|
+
<strong>The adaptive local multiplexer and fallback gateway for LLM coding agents.</strong><br>
|
|
7
|
+
One endpoint. Any agent. Zero downtime.
|
|
8
|
+
</p>
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<a href="https://pypi.org/project/omnimux/"><img src="https://img.shields.io/badge/pypi-omnimux-blue.svg?style=flat-square" alt="PyPI"></a>
|
|
12
|
+
<a href="https://python.org"><img src="https://img.shields.io/badge/python-3.12%2B-blue.svg?style=flat-square" alt="Python 3.12+"></a>
|
|
13
|
+
<a href="https://github.com/lucikritz/nexus-llm/actions"><img src="https://img.shields.io/badge/coverage-100%25-brightgreen.svg?style=flat-square" alt="Coverage"></a>
|
|
14
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg?style=flat-square" alt="License"></a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## What is omnimux?
|
|
20
|
+
|
|
21
|
+
When coding agents (Claude Code, Cursor, RooCode, Continue, Open WebUI, Aider) hit rate limits (`429`), credit exhaustion (`402`), or local VRAM out-of-memory errors, tasks break mid-refactor.
|
|
22
|
+
|
|
23
|
+
**omnimux** is a lightweight, database-first local gateway that sits between your coding tools and your LLM backends. It translates between OpenAI and Anthropic protocols, multiplexes API keys across providers, automatically hot-swaps to fallbacks upon failure, and unloads idle local models before loading new ones.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
Coding Agents (Claude Code, Cursor, RooCode, Continue, Aider)
|
|
27
|
+
│
|
|
28
|
+
[ omnimux gateway ]
|
|
29
|
+
│
|
|
30
|
+
┌───────────────┬──────────┴────────┬─────────────┬──────────┐
|
|
31
|
+
▼ ▼ ▼ ▼ ▼
|
|
32
|
+
Anthropic OpenRouter Gemini Groq Ollama
|
|
33
|
+
(Claude 3.5) (Failover pool) (Long context) (Fast) (Local zero-OOM)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Compatible Extensions & Agents
|
|
39
|
+
|
|
40
|
+
omnimux exposes standard OpenAI (`/v1/chat/completions`) and Anthropic (`/v1/messages`) endpoints, making it a drop-in replacement across tools:
|
|
41
|
+
|
|
42
|
+
| Agent / Tool | Protocol | Configuration |
|
|
43
|
+
| :--- | :--- | :--- |
|
|
44
|
+
| **Claude Code** | Native Anthropic (`/v1/messages`) | `export ANTHROPIC_BASE_URL="http://localhost:11444"`<br>`export ANTHROPIC_API_KEY="om_your_client_token"` |
|
|
45
|
+
| **Cursor** | OpenAI Compatible (`/v1`) | Base URL: `http://localhost:11444/v1`<br>API Key: `om_your_client_token` |
|
|
46
|
+
| **RooCode / Cline** | OpenAI / Anthropic format | Base URL: `http://localhost:11444/v1` or Anthropic Base URL |
|
|
47
|
+
| **Continue.dev** | OpenAI / Anthropic format | `apiBase: "http://localhost:11444/v1"` in `config.yaml` |
|
|
48
|
+
| **Open WebUI** | OpenAI Compatible (`/v1`) | Connections → OpenAI API: `http://localhost:11444/v1` |
|
|
49
|
+
| **Aider** | OpenAI Compatible (`/v1`) | `export OPENAI_API_BASE="http://localhost:11444/v1"`<br>`export OPENAI_API_KEY="om_your_client_token"` |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Core Features
|
|
54
|
+
|
|
55
|
+
- 🔄 **Autonomous 429 Hot-Swapping**: Automatically cycles through backup API keys and provider fallbacks with intelligent backoff cooldowns when an upstream service rate-limits or fails.
|
|
56
|
+
- 🦙 **Zero-OOM Ollama Engine**: Issues `keep_alive: 0` unloads for idle local models prior to spinning up fresh models, preventing out-of-memory crashes on unified-memory Apple Silicon and CUDA GPUs.
|
|
57
|
+
- ⚡ **Dual Protocol Translation**: Complete bidirectional translation between Anthropic `/v1/messages` and OpenAI `/v1/chat/completions` with full SSE streaming and token count endpoints.
|
|
58
|
+
- 🗜️ **Context Optimizer**: Converts bulky HTML pages from agent web searches into lean markdown and deduplicates multimodal vision images with local SHA-256 caching.
|
|
59
|
+
- 🔐 **Scoped Client Keys**: Generate isolated client tokens (`om_...`) per IDE extension with local database tracking, usage timestamps, and instant revocation.
|
|
60
|
+
- 📦 **Daemon & Single-Binary Simplicity**: Run seamlessly as a background service via `omnimux start -d` or inspect active logs with `omnimux status`.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Quickstart
|
|
65
|
+
|
|
66
|
+
### 1. Installation
|
|
67
|
+
|
|
68
|
+
Run via `uvx` (no installation required) or install with `pip`/`uv`:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Run immediately via uvx
|
|
72
|
+
uvx omnimux --help
|
|
73
|
+
|
|
74
|
+
# Or install globally into your environment
|
|
75
|
+
uv pip install omnimux
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
*(Note: `omni`, `nexus`, and `nexus-llm` are registered aliases for all CLI commands.)*
|
|
79
|
+
|
|
80
|
+
### 2. Discover & Register Models
|
|
81
|
+
|
|
82
|
+
Interactively discover and register models from local Ollama or upstream providers:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
omnimux model add
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 3. Start the Gateway
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Start in the foreground
|
|
92
|
+
omnimux start --port 11444
|
|
93
|
+
|
|
94
|
+
# Or start as a background daemon
|
|
95
|
+
omnimux start -d --port 11444
|
|
96
|
+
|
|
97
|
+
# Check status or stop
|
|
98
|
+
omnimux status
|
|
99
|
+
omnimux stop
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 4. Create an Agent Token
|
|
103
|
+
|
|
104
|
+
Create an isolated client key for your coding agent:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
omnimux keys create claude-code
|
|
108
|
+
# Output: om_k9A2x...
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Then point your agent:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
export ANTHROPIC_BASE_URL="http://localhost:11444"
|
|
115
|
+
export ANTHROPIC_API_KEY="om_k9A2x..."
|
|
116
|
+
claude
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## CLI Reference
|
|
122
|
+
|
|
123
|
+
| Command | Description |
|
|
124
|
+
| :--- | :--- |
|
|
125
|
+
| `omnimux start [--port PORT] [-d]` | Start the gateway server (foreground or daemon) |
|
|
126
|
+
| `omnimux stop` | Terminate running daemon process |
|
|
127
|
+
| `omnimux status` | Inspect health, PID, and port of running daemon |
|
|
128
|
+
| `omnimux model add` | Interactively query and register upstream models |
|
|
129
|
+
| `omnimux model list` | List registered platform models and endpoints |
|
|
130
|
+
| `omnimux keys create [name]` | Generate a client API token (`om_...`) |
|
|
131
|
+
| `omnimux keys list` | List active client keys and last used timestamps |
|
|
132
|
+
| `omnimux keys revoke <id>` | Revoke a client key |
|
|
133
|
+
| `omnimux export <file.json>` | Export entire SQLite routing state to JSON |
|
|
134
|
+
| `omnimux cache --clear` | Purge local image and request cache |
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Configuration
|
|
139
|
+
|
|
140
|
+
Settings can be overridden via environment variables or a local `.env` file:
|
|
141
|
+
|
|
142
|
+
```env
|
|
143
|
+
PORT=11444
|
|
144
|
+
SQLITE_DB_PATH=omnimux_state.db
|
|
145
|
+
PROXY_PASSWORD=optional_fallback_password
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Legacy `nx_...` keys and existing SQLite states are automatically recognized and supported for backwards compatibility.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT © LuciKritZ
|
|
Binary file
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"keys": {
|
|
3
|
+
"openrouter": [
|
|
4
|
+
{
|
|
5
|
+
"key_value": "sk-or-v1-abcdef1234567890",
|
|
6
|
+
"priority": 1,
|
|
7
|
+
"meta": {
|
|
8
|
+
"description": "Free tier account 1",
|
|
9
|
+
"models": [
|
|
10
|
+
"openrouter/openrouter/free",
|
|
11
|
+
"openrouter/openrouter/auto"
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"key_value": "sk-or-v1-0987654321fedcba",
|
|
17
|
+
"priority": 2,
|
|
18
|
+
"meta": {
|
|
19
|
+
"description": "Free tier account 2",
|
|
20
|
+
"models": [
|
|
21
|
+
"openrouter/openrouter/free"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"gemini": [
|
|
27
|
+
{
|
|
28
|
+
"key_value": "AIzaSyB1234567890abcdefghijklmnopqrstuv",
|
|
29
|
+
"priority": 1,
|
|
30
|
+
"meta": {
|
|
31
|
+
"description": "Google AI Studio Credits"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"groq": [
|
|
36
|
+
{
|
|
37
|
+
"key_value": "gsk_1234567890abcdefghijklmnopqrstuvwxyz",
|
|
38
|
+
"priority": 1
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"tokenharbor": [
|
|
42
|
+
{
|
|
43
|
+
"key_value": "th-sk-sample1234567890",
|
|
44
|
+
"priority": 1,
|
|
45
|
+
"meta": {
|
|
46
|
+
"api_base": "https://api.tokenharbor.com",
|
|
47
|
+
"protocol": "anthropic",
|
|
48
|
+
"models": {
|
|
49
|
+
"opus": "inclusionai/ling-3.1-flash",
|
|
50
|
+
"sonnet": "nvidia/nemotron-3-super-120b-a12b:free",
|
|
51
|
+
"haiku": "nvidia/nemotron-3-ultra-550b-a55b:free"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
"models": {
|
|
58
|
+
"taken-from": "https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json",
|
|
59
|
+
"gemini/gemini-2.5-flash-lite": {
|
|
60
|
+
"max_input_tokens": 1048576,
|
|
61
|
+
"max_output_tokens": 65535,
|
|
62
|
+
"mode": "chat",
|
|
63
|
+
"supports_vision": true
|
|
64
|
+
},
|
|
65
|
+
"groq/llama-3.1-8b-instant": {
|
|
66
|
+
"max_input_tokens": 128000,
|
|
67
|
+
"max_output_tokens": 8192,
|
|
68
|
+
"mode": "chat",
|
|
69
|
+
"supports_vision": false
|
|
70
|
+
},
|
|
71
|
+
"openrouter/openrouter/free": {
|
|
72
|
+
"max_input_tokens": 200000,
|
|
73
|
+
"mode": "chat",
|
|
74
|
+
"supports_vision": true
|
|
75
|
+
},
|
|
76
|
+
"ollama/qwen2.5:1.5b": {
|
|
77
|
+
"max_input_tokens": 32768,
|
|
78
|
+
"max_output_tokens": 8192,
|
|
79
|
+
"mode": "chat",
|
|
80
|
+
"supports_vision": false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
echo "This script will install omnimux as a background macOS service (LaunchAgent)."
|
|
6
|
+
read -p "Do you want to proceed? (y/n) " -n 1 -r
|
|
7
|
+
echo
|
|
8
|
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
9
|
+
echo "Aborted."
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
if [[ "$(uname)" != "Darwin" ]]; then
|
|
14
|
+
echo "Error: This service installation is only supported on macOS."
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
PLIST_PATH="$HOME/Library/LaunchAgents/com.lucikritz.omnimux.plist"
|
|
19
|
+
PROJECT_DIR="$(pwd)"
|
|
20
|
+
UV_PATH="$(which uv)"
|
|
21
|
+
|
|
22
|
+
# Create the logs directory
|
|
23
|
+
mkdir -p "$PROJECT_DIR/.logs"
|
|
24
|
+
|
|
25
|
+
if [ -z "$UV_PATH" ]; then
|
|
26
|
+
echo "Error: uv command not found in PATH."
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# Create the plist
|
|
31
|
+
cat <<EOF > "$PLIST_PATH"
|
|
32
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
33
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
34
|
+
<plist version="1.0">
|
|
35
|
+
<dict>
|
|
36
|
+
<key>Label</key>
|
|
37
|
+
<string>com.lucikritz.omnimux</string>
|
|
38
|
+
<key>ProgramArguments</key>
|
|
39
|
+
<array>
|
|
40
|
+
<string>$UV_PATH</string>
|
|
41
|
+
<string>run</string>
|
|
42
|
+
<string>python</string>
|
|
43
|
+
<string>-m</string>
|
|
44
|
+
<string>omnimux</string>
|
|
45
|
+
</array>
|
|
46
|
+
<key>WorkingDirectory</key>
|
|
47
|
+
<string>$PROJECT_DIR</string>
|
|
48
|
+
<key>RunAtLoad</key>
|
|
49
|
+
<true/>
|
|
50
|
+
<key>KeepAlive</key>
|
|
51
|
+
<true/>
|
|
52
|
+
<key>StandardOutPath</key>
|
|
53
|
+
<string>$PROJECT_DIR/.logs/omnimux.log</string>
|
|
54
|
+
<key>StandardErrorPath</key>
|
|
55
|
+
<string>$PROJECT_DIR/.logs/omnimux.err.log</string>
|
|
56
|
+
</dict>
|
|
57
|
+
</plist>
|
|
58
|
+
EOF
|
|
59
|
+
|
|
60
|
+
echo "Created LaunchAgent at $PLIST_PATH"
|
|
61
|
+
|
|
62
|
+
# Setup aliases
|
|
63
|
+
SHELL_RC="$HOME/.zshrc"
|
|
64
|
+
if [[ "$SHELL" == *"bash"* ]]; then
|
|
65
|
+
SHELL_RC="$HOME/.bashrc"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
if ! grep -q "omnimux-start" "$SHELL_RC"; then
|
|
69
|
+
echo "" >> "$SHELL_RC"
|
|
70
|
+
echo "# omnimux aliases" >> "$SHELL_RC"
|
|
71
|
+
echo "alias omnimux-start=\"launchctl bootstrap gui/\\\$(id -u) $PLIST_PATH\"" >> "$SHELL_RC"
|
|
72
|
+
echo "alias omnimux-stop=\"launchctl bootout gui/\\\$(id -u) $PLIST_PATH\"" >> "$SHELL_RC"
|
|
73
|
+
echo "alias omnimux-log=\"tail -f $PROJECT_DIR/.logs/omnimux.log\"" >> "$SHELL_RC"
|
|
74
|
+
echo "Aliases added to $SHELL_RC. Run 'source $SHELL_RC' to load them."
|
|
75
|
+
else
|
|
76
|
+
echo "Aliases already exist in $SHELL_RC"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
echo "Installation complete! Run 'source $SHELL_RC' then 'omnimux-start' to launch the service."
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "omnimux"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Local intelligent LLM multiplexer, proxy, and router"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"aiosqlite>=0.20.0",
|
|
9
|
+
"beautifulsoup4>=4.14.3",
|
|
10
|
+
"fastapi>=0.136.3",
|
|
11
|
+
"httpx>=0.28.1",
|
|
12
|
+
"markdownify>=1.2.2",
|
|
13
|
+
"pydantic-settings>=2.14.1",
|
|
14
|
+
"python-dotenv>=1.2.2",
|
|
15
|
+
"uvicorn>=0.48.0",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
omnimux = "omnimux.__main__:main"
|
|
20
|
+
omni = "omnimux.__main__:main"
|
|
21
|
+
nexus = "omnimux.__main__:main"
|
|
22
|
+
nexus-llm = "omnimux.__main__:main"
|
|
23
|
+
|
|
24
|
+
[dependency-groups]
|
|
25
|
+
dev = [
|
|
26
|
+
"mypy>=2.1.0",
|
|
27
|
+
"pre-commit>=4.6.0",
|
|
28
|
+
"pytest>=9.0.3",
|
|
29
|
+
"pytest-asyncio>=1.4.0",
|
|
30
|
+
"pytest-benchmark>=5.2.3",
|
|
31
|
+
"pytest-cov>=7.1.0",
|
|
32
|
+
"ruff>=0.15.14",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[build-system]
|
|
36
|
+
requires = ["hatchling"]
|
|
37
|
+
build-backend = "hatchling.build"
|
|
38
|
+
|
|
39
|
+
[tool.ruff]
|
|
40
|
+
target-version = "py312"
|
|
41
|
+
line-length = 100
|
|
42
|
+
|
|
43
|
+
[tool.ruff.lint]
|
|
44
|
+
select = ["E", "W", "F", "I", "N", "UP", "B", "SIM", "RUF"]
|
|
45
|
+
|
|
46
|
+
[tool.ruff.lint.isort]
|
|
47
|
+
known-first-party = ["omnimux"]
|
|
48
|
+
|
|
49
|
+
[tool.mypy]
|
|
50
|
+
python_version = "3.12"
|
|
51
|
+
strict = true
|
|
52
|
+
warn_return_any = true
|
|
53
|
+
warn_unused_configs = true
|
|
54
|
+
disallow_untyped_defs = true
|
|
55
|
+
|
|
56
|
+
[tool.coverage.run]
|
|
57
|
+
source = ["omnimux"]
|
|
58
|
+
|
|
59
|
+
[tool.coverage.report]
|
|
60
|
+
fail_under = 100
|
|
61
|
+
show_missing = true
|
|
62
|
+
exclude_lines = [
|
|
63
|
+
"pragma: no cover",
|
|
64
|
+
"if TYPE_CHECKING:",
|
|
65
|
+
"if __name__ == .__main__.:",
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
[tool.pytest.ini_options]
|
|
69
|
+
asyncio_mode = "auto"
|
|
File without changes
|