eggpool 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eggpool/__init__.py +5 -0
- eggpool/__main__.py +7 -0
- eggpool/_share/.env.example +9 -0
- eggpool/_share/config.example.toml +551 -0
- eggpool/accounts/__init__.py +1 -0
- eggpool/accounts/registry.py +181 -0
- eggpool/accounts/state.py +170 -0
- eggpool/api/__init__.py +1 -0
- eggpool/api/chat_completions.py +31 -0
- eggpool/api/errors.py +51 -0
- eggpool/api/messages.py +31 -0
- eggpool/api/models.py +76 -0
- eggpool/api/proxy_request.py +242 -0
- eggpool/api/stats.py +284 -0
- eggpool/app.py +893 -0
- eggpool/auth.py +92 -0
- eggpool/background/__init__.py +147 -0
- eggpool/background/cleanup.py +206 -0
- eggpool/catalog/__init__.py +1 -0
- eggpool/catalog/cache.py +645 -0
- eggpool/catalog/fetcher.py +188 -0
- eggpool/catalog/limits.py +306 -0
- eggpool/catalog/normalizer.py +119 -0
- eggpool/catalog/pricing.py +438 -0
- eggpool/catalog/protocols.py +194 -0
- eggpool/catalog/service.py +920 -0
- eggpool/cli.py +1481 -0
- eggpool/constants.py +34 -0
- eggpool/dashboard/__init__.py +3 -0
- eggpool/dashboard/_resources.py +14 -0
- eggpool/dashboard/escape.py +104 -0
- eggpool/dashboard/render.py +1448 -0
- eggpool/dashboard/routes.py +451 -0
- eggpool/dashboard/static/chart.umd.min.js +20 -0
- eggpool/dashboard/static/dashboard.css +341 -0
- eggpool/dashboard/static/favicon.svg +23 -0
- eggpool/dashboard/theme.py +514 -0
- eggpool/dashboard/themes/Booberry.toml +42 -0
- eggpool/dashboard/themes/Catppuccin Latte.toml +42 -0
- eggpool/dashboard/themes/Catppuccin Macchiato.toml +42 -0
- eggpool/dashboard/themes/Catppuccin Mocha.toml +42 -0
- eggpool/dashboard/themes/Cyber Red.toml +42 -0
- eggpool/dashboard/themes/Cyberpunk.toml +42 -0
- eggpool/dashboard/themes/Dark Green.toml +43 -0
- eggpool/dashboard/themes/Discord (80_ Saturation).toml +50 -0
- eggpool/dashboard/themes/Discord.toml +50 -0
- eggpool/dashboard/themes/Dracula.toml +42 -0
- eggpool/dashboard/themes/Ferra Light.toml +42 -0
- eggpool/dashboard/themes/Flexor Dark.toml +42 -0
- eggpool/dashboard/themes/Gruvbox.toml +42 -0
- eggpool/dashboard/themes/Halcyon Dark.toml +42 -0
- eggpool/dashboard/themes/IntelliJ Light.toml +42 -0
- eggpool/dashboard/themes/Kanagawa.toml +42 -0
- eggpool/dashboard/themes/Macaw Dark.toml +42 -0
- eggpool/dashboard/themes/Macaw Light.toml +42 -0
- eggpool/dashboard/themes/Matrix.toml +42 -0
- eggpool/dashboard/themes/Noctis Lilac.toml +42 -0
- eggpool/dashboard/themes/Nord.toml +42 -0
- eggpool/dashboard/themes/Nostromo Terminal.toml +42 -0
- eggpool/dashboard/themes/One Dark.toml +42 -0
- eggpool/dashboard/themes/Oxocarbon.toml +42 -0
- eggpool/dashboard/themes/Rose Pine Dawn.toml +42 -0
- eggpool/dashboard/themes/Rose Pine Moon.toml +42 -0
- eggpool/dashboard/themes/Rose Pine.toml +42 -0
- eggpool/dashboard/themes/Solarized Dark.toml +42 -0
- eggpool/dashboard/themes/Sonokai.toml +42 -0
- eggpool/dashboard/themes/Tokyo Night Storm.toml +42 -0
- eggpool/dashboard/themes/VESPER.toml +42 -0
- eggpool/dashboard/themes/Zenburn.toml +42 -0
- eggpool/dashboard/themes/acton.toml +42 -0
- eggpool/dashboard/themes/bam.toml +42 -0
- eggpool/dashboard/themes/base16-atelier-forest-light.toml +45 -0
- eggpool/dashboard/themes/berlin.toml +42 -0
- eggpool/dashboard/themes/black but with important highlights.toml +42 -0
- eggpool/dashboard/themes/broc.toml +42 -0
- eggpool/dashboard/themes/cork.toml +42 -0
- eggpool/dashboard/themes/ferra.toml +42 -0
- eggpool/dashboard/themes/forest.toml +42 -0
- eggpool/dashboard/themes/lisbon.toml +42 -0
- eggpool/dashboard/themes/midnight.toml +42 -0
- eggpool/dashboard/themes/oslo.toml +42 -0
- eggpool/dashboard/themes/plum.toml +43 -0
- eggpool/dashboard/themes/portland.toml +42 -0
- eggpool/dashboard/themes/sunset.toml +42 -0
- eggpool/dashboard/themes/tofino.toml +42 -0
- eggpool/dashboard/themes/vanimo.toml +42 -0
- eggpool/dashboard/themes/vik.toml +42 -0
- eggpool/db/__init__.py +0 -0
- eggpool/db/connection.py +401 -0
- eggpool/db/migrations.py +125 -0
- eggpool/db/repositories.py +989 -0
- eggpool/db/schema/0001_initial.sql +78 -0
- eggpool/db/schema/0002_indexes.sql +24 -0
- eggpool/db/schema/0003_request_attempts.sql +20 -0
- eggpool/db/schema/0004_integration_hardening.sql +34 -0
- eggpool/db/schema/0005_price_microdollars.sql +15 -0
- eggpool/db/schema/0006_correct_price_microdollars.sql +12 -0
- eggpool/db/schema/0007_price_cache_rates.sql +5 -0
- eggpool/db/schema/0008_proxy_request_identity.sql +9 -0
- eggpool/db/schema/0009_model_protocol_source.sql +3 -0
- eggpool/db/schema/0010_health_probe.sql +5 -0
- eggpool/db/schema/0011_model_resolution_status.sql +1 -0
- eggpool/db/schema/0012_drop_reservations_estimated_microdollars.sql +6 -0
- eggpool/db/schema/0013_request_attempts_account_id_index.sql +7 -0
- eggpool/db/schema/0014_bandwidth_tracking.sql +2 -0
- eggpool/db/schema/0015_multi_provider.sql +23 -0
- eggpool/db/schema/0016_requests_provider_id.sql +2 -0
- eggpool/db/schema/0017_price_snapshots_provider_id.sql +4 -0
- eggpool/db/schema/0018_provider_pings.sql +16 -0
- eggpool/db/schema/0019_client_ip.sql +4 -0
- eggpool/db/schema/0020_performance_indexes.sql +19 -0
- eggpool/db/schema/0021_provider_model_metadata.sql +19 -0
- eggpool/db/schema/0022_dashboard_indexes.sql +14 -0
- eggpool/db/schema/checksums.json +26 -0
- eggpool/deploy/__init__.py +126 -0
- eggpool/errors.py +123 -0
- eggpool/health/__init__.py +8 -0
- eggpool/health/circuit_breaker.py +146 -0
- eggpool/health/health_manager.py +339 -0
- eggpool/integrations/__init__.py +1 -0
- eggpool/integrations/opencode.py +90 -0
- eggpool/logging.py +50 -0
- eggpool/models/__init__.py +0 -0
- eggpool/models/api.py +32 -0
- eggpool/models/config.py +658 -0
- eggpool/models/database.py +99 -0
- eggpool/models/domain.py +59 -0
- eggpool/onboard.py +111 -0
- eggpool/providers/__init__.py +0 -0
- eggpool/providers/_templates.toml +574 -0
- eggpool/providers/client_pool.py +131 -0
- eggpool/providers/connect.py +988 -0
- eggpool/providers/contract.py +91 -0
- eggpool/providers/pproxy_transport.py +293 -0
- eggpool/proxy/__init__.py +1 -0
- eggpool/proxy/client.py +140 -0
- eggpool/proxy/sse_observer.py +283 -0
- eggpool/proxy/usage.py +114 -0
- eggpool/py.typed +1 -0
- eggpool/quota/__init__.py +13 -0
- eggpool/quota/estimation.py +639 -0
- eggpool/quota/reservation.py +193 -0
- eggpool/quota/scorer.py +215 -0
- eggpool/request/__init__.py +13 -0
- eggpool/request/attempt_finalizer.py +125 -0
- eggpool/request/body.py +70 -0
- eggpool/request/coordinator.py +1638 -0
- eggpool/request/finalizer.py +392 -0
- eggpool/request/limits.py +152 -0
- eggpool/retry/__init__.py +7 -0
- eggpool/retry/classification.py +207 -0
- eggpool/routing/__init__.py +1 -0
- eggpool/routing/eligibility.py +95 -0
- eggpool/routing/provider.py +20 -0
- eggpool/routing/router.py +395 -0
- eggpool/security/__init__.py +7 -0
- eggpool/security/redaction.py +327 -0
- eggpool/stats/__init__.py +37 -0
- eggpool/stats/queries.py +598 -0
- eggpool/stats/service.py +548 -0
- eggpool/toml_edit.py +101 -0
- eggpool-0.1.0.dist-info/METADATA +512 -0
- eggpool-0.1.0.dist-info/RECORD +166 -0
- eggpool-0.1.0.dist-info/WHEEL +4 -0
- eggpool-0.1.0.dist-info/entry_points.txt +2 -0
- eggpool-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: eggpool
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A lightweight proxy that aggregates multiple LLM provider accounts behind one OpenAI-compatible endpoint
|
|
5
|
+
Project-URL: Homepage, https://github.com/eggstack/eggpool
|
|
6
|
+
Project-URL: Repository, https://github.com/eggstack/eggpool
|
|
7
|
+
Project-URL: Issues, https://github.com/eggstack/eggpool/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/eggstack/eggpool/tree/main/docs
|
|
9
|
+
Project-URL: Changelog, https://github.com/eggstack/eggpool/blob/main/CHANGELOG.md
|
|
10
|
+
Author-email: David Bowman <dbowman91@proton.me>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: aggregation,anthropic,llm,multi-account,openai,opencode,proxy,router
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Framework :: AsyncIO
|
|
16
|
+
Classifier: Framework :: FastAPI
|
|
17
|
+
Classifier: Intended Audience :: System Administrators
|
|
18
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
19
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
20
|
+
Classifier: Programming Language :: Python :: 3
|
|
21
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: System :: Monitoring
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.12
|
|
26
|
+
Requires-Dist: aiosqlite
|
|
27
|
+
Requires-Dist: click
|
|
28
|
+
Requires-Dist: fastapi
|
|
29
|
+
Requires-Dist: granian
|
|
30
|
+
Requires-Dist: httpx
|
|
31
|
+
Requires-Dist: pproxy>=2.7.9
|
|
32
|
+
Requires-Dist: pydantic>=2.0
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: coverage[toml]; extra == 'dev'
|
|
35
|
+
Requires-Dist: pre-commit; extra == 'dev'
|
|
36
|
+
Requires-Dist: pyright; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
40
|
+
Requires-Dist: respx; extra == 'dev'
|
|
41
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
42
|
+
Description-Content-Type: text/markdown
|
|
43
|
+
|
|
44
|
+
[](https://pypi.org/project/eggpool/)
|
|
45
|
+
[](https://www.python.org/)
|
|
46
|
+
[](LICENSE)
|
|
47
|
+
[](https://github.com/eggstack/eggpool/actions/workflows/ci.yml)
|
|
48
|
+
|
|
49
|
+
# EggPool
|
|
50
|
+
|
|
51
|
+
A lightweight, LAN-hosted proxy that aggregates multiple AI provider accounts
|
|
52
|
+
behind one OpenAI/Anthropic-compatible endpoint.
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
|
|
56
|
+
- Transparently proxies model requests across multiple providers
|
|
57
|
+
- Supports OpenAI-compatible and Anthropic-compatible upstream request paths
|
|
58
|
+
- Dynamically discovers currently available models from each provider
|
|
59
|
+
- Routes requests across accounts based on estimated quota utilization
|
|
60
|
+
- Per-account outbound proxy support via [pproxy](https://pypi.org/project/pproxy/) (SOCKS5, HTTP, or any pproxy URI)
|
|
61
|
+
- Tracks request, token, model, latency, error, and estimated-cost statistics in SQLite
|
|
62
|
+
- Multi-page dashboard with overview, accounts, models, latency, pings, events, timeseries, and bandwidth views
|
|
63
|
+
- 50+ themes from [Halloy](https://github.com/squidowl/halloy) and [Chart.js](https://www.chartjs.org/) v4 (MIT) for dashboard charts
|
|
64
|
+
- Designed for lightweight deployments such as Raspberry Pis
|
|
65
|
+
|
|
66
|
+
## Requirements
|
|
67
|
+
|
|
68
|
+
- Python 3.12+
|
|
69
|
+
- [uv](https://docs.astral.sh/uv/) for dependency management
|
|
70
|
+
|
|
71
|
+
## Quick Start
|
|
72
|
+
|
|
73
|
+
### Option 1: pipx install (recommended)
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
pipx install eggpool
|
|
77
|
+
pipx run eggpool --help # or just `eggpool --help` if PATH includes it
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`pipx` installs `eggpool` into its own venv and exposes the
|
|
81
|
+
`eggpool` console script globally. The bundled themes and
|
|
82
|
+
provider templates ship inside the package — no extra files
|
|
83
|
+
required to start.
|
|
84
|
+
|
|
85
|
+
Then copy and edit configuration:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
cp /path/to/your/eggpool-venv/lib/python*/site-packages/eggpool/_share/config.example.toml ~/.config/eggpool/config.toml
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Or use the built-in helper:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
eggpool init-config
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Option 2: Automated install
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
curl -fsSL https://raw.githubusercontent.com/eggstack/eggpool/main/scripts/install.sh | bash
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The script:
|
|
104
|
+
|
|
105
|
+
- Downloads the repository if not running from a clone
|
|
106
|
+
- Installs `uv` if missing
|
|
107
|
+
- Verifies Python 3.12+
|
|
108
|
+
- Installs dependencies
|
|
109
|
+
- Copies example configuration files
|
|
110
|
+
- Attempts configuration validation
|
|
111
|
+
|
|
112
|
+
Validation fails until `.env` contains real, non-placeholder keys. Edit
|
|
113
|
+
`config.toml` and `.env`, then run the validation and migration commands below.
|
|
114
|
+
|
|
115
|
+
### Option 3: Manual install
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Install dependencies, including local development tools
|
|
119
|
+
uv sync --extra dev
|
|
120
|
+
|
|
121
|
+
# Copy and edit configuration
|
|
122
|
+
cp config.example.toml config.toml
|
|
123
|
+
cp .env.example .env
|
|
124
|
+
|
|
125
|
+
# Edit config.toml for providers/accounts and .env for keys.
|
|
126
|
+
# check-config rejects placeholder values such as "your-api-key".
|
|
127
|
+
|
|
128
|
+
# Validate configuration
|
|
129
|
+
set -a; source .env; set +a
|
|
130
|
+
uv run eggpool --config config.toml check-config
|
|
131
|
+
|
|
132
|
+
# Run database migrations
|
|
133
|
+
uv run eggpool --config config.toml migrate
|
|
134
|
+
|
|
135
|
+
# Start the server
|
|
136
|
+
uv run eggpool --config config.toml serve
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Option 4: Interactive setup
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Run the interactive onboarding wizard
|
|
143
|
+
uv run eggpool onboard
|
|
144
|
+
|
|
145
|
+
# Or connect to a specific provider
|
|
146
|
+
uv run eggpool connect
|
|
147
|
+
uv run eggpool connect list
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## CLI Commands
|
|
151
|
+
|
|
152
|
+
| Command | Description |
|
|
153
|
+
|---------|-------------|
|
|
154
|
+
| `eggpool serve` | Start the aggregation proxy server (default command) |
|
|
155
|
+
| `eggpool check-config` | Validate the configuration file |
|
|
156
|
+
| `eggpool migrate` | Run database migrations |
|
|
157
|
+
| `eggpool onboard` | Run the interactive onboarding setup |
|
|
158
|
+
| `eggpool connect` | Connect to a new provider interactively |
|
|
159
|
+
| `eggpool connect list` | List available providers for connection |
|
|
160
|
+
| `eggpool logout` | Remove a configured provider account |
|
|
161
|
+
| `eggpool rehash` | Restart the server to apply configuration changes |
|
|
162
|
+
| `eggpool restart` | Fully restart the server (stop then start) |
|
|
163
|
+
| `eggpool stop` | Stop the running server |
|
|
164
|
+
| `eggpool set` | Set a server configuration value and restart |
|
|
165
|
+
| `eggpool getkey` | Print the current server API key |
|
|
166
|
+
| `eggpool newkey` | Generate a new server API key |
|
|
167
|
+
| `eggpool edit` | Open the configuration file in the default editor |
|
|
168
|
+
| `eggpool configsetup` | Print configuration snippets for code editors |
|
|
169
|
+
| `eggpool configsetup opencode` | Print OpenCode provider config JSON with model limits |
|
|
170
|
+
| `eggpool configsetup claude-code` | Print Claude Code config snippet |
|
|
171
|
+
| `eggpool update` | Check for updates and reinstall if newer |
|
|
172
|
+
| `eggpool models refresh` | Refresh the model catalog from upstream |
|
|
173
|
+
| `eggpool accounts status` | Show configured account status |
|
|
174
|
+
| `eggpool accounts list` | List configured provider accounts |
|
|
175
|
+
| `eggpool dashboard public` | Toggle dashboard public access |
|
|
176
|
+
| `eggpool db vacuum` | Vacuum the database to reclaim space |
|
|
177
|
+
| `eggpool init-config` | Write bundled config.example.toml to current directory or TARGET |
|
|
178
|
+
| `eggpool deploy systemd` | Print the systemd unit + install instructions |
|
|
179
|
+
| `eggpool deploy logrotate` | Print the logrotate config + install instructions |
|
|
180
|
+
| `eggpool deploy cron` | Print the daily-backup cron entry + install instructions |
|
|
181
|
+
| `eggpool deploy all` | Print every deployment snippet in sequence |
|
|
182
|
+
|
|
183
|
+
All commands accept `--config /path/to/config.toml` (defaults to `config.toml`).
|
|
184
|
+
Configuration changes require a service restart; live reload is intentionally
|
|
185
|
+
not supported.
|
|
186
|
+
|
|
187
|
+
## Operational Scripts
|
|
188
|
+
|
|
189
|
+
Scripts under `scripts/`:
|
|
190
|
+
|
|
191
|
+
- `scripts/install.sh` — quick install script for local development setup
|
|
192
|
+
- `scripts/install_prompt.py` — installation prompt helper
|
|
193
|
+
- `scripts/check_database.py` — read-only database invariant checker. See
|
|
194
|
+
`docs/deployment.md` for the documented exit-code contract.
|
|
195
|
+
- `scripts/smoke_test.py` — deployment smoke test for the running
|
|
196
|
+
proxy. Exercises health, models, stats, non-streaming, and
|
|
197
|
+
streaming endpoints for both protocol families.
|
|
198
|
+
- `scripts/verify_upstream_auth.py` — direct-upstream authentication
|
|
199
|
+
verifier. Bypasses EggPool to confirm the configured key works
|
|
200
|
+
against each upstream endpoint family. Operator-only; not run in CI.
|
|
201
|
+
|
|
202
|
+
## API Endpoints
|
|
203
|
+
|
|
204
|
+
### Data Plane (require local API key)
|
|
205
|
+
|
|
206
|
+
| Method | Path | Description |
|
|
207
|
+
|--------|------|-------------|
|
|
208
|
+
| `GET` | `/v1/models` | List available models |
|
|
209
|
+
| `POST` | `/v1/chat/completions` | OpenAI-compatible chat completions |
|
|
210
|
+
| `POST` | `/v1/messages` | Anthropic-compatible messages |
|
|
211
|
+
|
|
212
|
+
### Health
|
|
213
|
+
|
|
214
|
+
| Method | Path | Description |
|
|
215
|
+
|--------|------|-------------|
|
|
216
|
+
| `GET` | `/v1/healthz` | Liveness check |
|
|
217
|
+
| `GET` | `/v1/readyz` | Readiness check (database, accounts, catalog) |
|
|
218
|
+
|
|
219
|
+
### Dashboard and Stats
|
|
220
|
+
|
|
221
|
+
When `[dashboard].enabled = true`, the dashboard is served at `/`. It defaults
|
|
222
|
+
to the bundled `Cyber Red` theme and refreshes visible data in place using the
|
|
223
|
+
configured `[dashboard].refresh_interval_s`.
|
|
224
|
+
|
|
225
|
+
The dashboard includes:
|
|
226
|
+
- Overview with request counts, error rates, costs, and token usage
|
|
227
|
+
- Account and model breakdowns with filtering
|
|
228
|
+
- Latency metrics including time-to-first-token (TTFT)
|
|
229
|
+
- Provider health monitoring with ping statistics
|
|
230
|
+
- Bandwidth heatmap (GitHub-style contribution graph)
|
|
231
|
+
- Timeseries charts with auto-refresh
|
|
232
|
+
- Interactive theme selector with 50+ [Halloy](https://themes.halloy.chat/) themes
|
|
233
|
+
|
|
234
|
+
Static assets (CSS, JavaScript, favicon) are served from `/static/` with
|
|
235
|
+
appropriate cache headers.
|
|
236
|
+
|
|
237
|
+
JSON stats endpoints are available under `/api/stats/*`, including summary,
|
|
238
|
+
accounts, models, timeseries, errors, latency, pings, bandwidth, and `/api/events`.
|
|
239
|
+
|
|
240
|
+
## Configuration
|
|
241
|
+
|
|
242
|
+
Configuration uses a single TOML file. API keys are loaded from environment variables.
|
|
243
|
+
|
|
244
|
+
See `config.example.toml` for all available options.
|
|
245
|
+
|
|
246
|
+
### Key Sections
|
|
247
|
+
|
|
248
|
+
- `[server]` — Bind address, port (default 11300), API key, logging
|
|
249
|
+
- `[upstream]` — Upstream API base URL, timeouts, connection pool
|
|
250
|
+
- `[database]` — SQLite path, WAL mode, synchronous mode
|
|
251
|
+
- `[models]` — Catalog refresh interval, exposure mode, staleness settings, `collapse_models` flag
|
|
252
|
+
- `[routing]` — Routing strategy, retry limits, penalties
|
|
253
|
+
- `[limits]` — Quota windows (5-hour, weekly, monthly)
|
|
254
|
+
- `[dashboard]` — Dashboard toggle, theme, retention, refresh interval
|
|
255
|
+
- `[security]` — Allowed hosts, CORS, header redaction
|
|
256
|
+
- `[providers.*]` — Provider configurations with accounts and `routing_priority`
|
|
257
|
+
- `[proxies.*]` — Named outbound proxy definitions (pproxy URI syntax)
|
|
258
|
+
- `[model_overrides.*]` — Per-model protocol or path overrides
|
|
259
|
+
|
|
260
|
+
### Provider Configuration
|
|
261
|
+
|
|
262
|
+
Providers are configured under `[providers.<id>]` with nested `[[providers.<id>.accounts]]` entries:
|
|
263
|
+
|
|
264
|
+
```toml
|
|
265
|
+
[providers.opencode-go]
|
|
266
|
+
id = "opencode-go"
|
|
267
|
+
base_url = "https://opencode.ai/zen/go/v1"
|
|
268
|
+
protocols = ["openai", "anthropic"]
|
|
269
|
+
|
|
270
|
+
[[providers.opencode-go.accounts]]
|
|
271
|
+
name = "personal"
|
|
272
|
+
api_key = "sk-your-opencode-go-key"
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Use `eggpool connect` for interactive provider setup instead of manual configuration. Each provider account is auto-labeled with `routing_priority = 0` on first `eggpool connect`, so operators can rebalance later by editing `[providers.<id>].routing_priority` and restarting the service. See [docs/providers.md](docs/providers.md) for the full provider catalog with status definitions, verification commands, and provider-specific notes.
|
|
276
|
+
|
|
277
|
+
### Routing Priority and Model Collapse
|
|
278
|
+
|
|
279
|
+
Two related knobs control how requests for the same base model fan out across
|
|
280
|
+
providers and how the model appears in `/v1/models`:
|
|
281
|
+
|
|
282
|
+
- `[providers.<id>].routing_priority` — non-negative integer (default `0`).
|
|
283
|
+
Higher values are preferred. Accounts inside a tier are still load-balanced
|
|
284
|
+
by the existing quota-fair scorer.
|
|
285
|
+
- `[models].collapse_models` — boolean (default `false`). When `false`, the
|
|
286
|
+
catalog exposes one provider-suffixed entry per `(model_id, provider_id)`.
|
|
287
|
+
When `true`, the same base model collapses to a single unsuffixed ID and is
|
|
288
|
+
routed across every provider that supports it.
|
|
289
|
+
|
|
290
|
+
`eggpool configsetup opencode` output reflects the current `collapse_models`
|
|
291
|
+
setting: suffixed IDs when `false`, unsuffixed when `true`. See
|
|
292
|
+
[docs/providers.md](docs/providers.md) for the full worked example with three
|
|
293
|
+
providers and three priorities.
|
|
294
|
+
|
|
295
|
+
### Per-Account Outbound Proxy
|
|
296
|
+
|
|
297
|
+
Each account can route upstream traffic through a [pproxy](https://pypi.org/project/pproxy/)-compatible outbound proxy. This is useful for geo-routing, residential IP rotation, or isolating provider traffic by account.
|
|
298
|
+
|
|
299
|
+
Three mutually exclusive fields on each account control the proxy:
|
|
300
|
+
|
|
301
|
+
| Field | Description |
|
|
302
|
+
|-------|-------------|
|
|
303
|
+
| `proxy` | Reference a named entry from `[proxies.*]` |
|
|
304
|
+
| `proxy_url` | Inline pproxy URI (use when the URI has no credentials) |
|
|
305
|
+
| `proxy_url_env` | Environment variable name holding the pproxy URI (use when the URI contains credentials) |
|
|
306
|
+
|
|
307
|
+
**Quick example — inline SOCKS5 proxy:**
|
|
308
|
+
|
|
309
|
+
```toml
|
|
310
|
+
[[providers.opencode-go.accounts]]
|
|
311
|
+
name = "personal"
|
|
312
|
+
api_key = "sk-your-key"
|
|
313
|
+
proxy_url = "socks5://127.0.0.1:1080"
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**Named proxy with env-var credentials:**
|
|
317
|
+
|
|
318
|
+
```toml
|
|
319
|
+
[proxies.residential-us]
|
|
320
|
+
url_env = "MY_RESIDENTIAL_PROXY_URL"
|
|
321
|
+
|
|
322
|
+
[[providers.opencode-go.accounts]]
|
|
323
|
+
name = "personal"
|
|
324
|
+
api_key = "sk-your-key"
|
|
325
|
+
proxy = "residential-us"
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
The `proxy` field references a `[proxies.<name>]` entry, keeping credentials out of the config file. See `docs/proxy.md` for the full pproxy URI syntax and more examples.
|
|
329
|
+
|
|
330
|
+
### Model Limits
|
|
331
|
+
|
|
332
|
+
EggPool supports configurable effective context limits for individual models on individual providers. This lets operators advertise a smaller context window than the provider physically supports, causing OpenCode to compact before reaching expensive long-context regimes.
|
|
333
|
+
|
|
334
|
+
**Global overrides** apply to all providers:
|
|
335
|
+
|
|
336
|
+
```toml
|
|
337
|
+
[model_overrides."model-id"]
|
|
338
|
+
max_context_tokens = 200000
|
|
339
|
+
max_output_tokens = 16384
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
**Provider-specific overrides** take precedence per field:
|
|
343
|
+
|
|
344
|
+
```toml
|
|
345
|
+
[providers.opencode-go.model_overrides."MiniMax-M3"]
|
|
346
|
+
max_context_tokens = 220000
|
|
347
|
+
max_output_tokens = 16384
|
|
348
|
+
enforce_context_limit = true
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
When the same model is served by multiple providers, unsuffixed model exposure uses the conservative minimum across all providers.
|
|
352
|
+
|
|
353
|
+
To generate an OpenCode configuration with explicit model limits:
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
eggpool configsetup opencode --json-only > opencode-config.json
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Merge the generated provider definition into your OpenCode configuration. OpenCode must consume these model definitions for proactive compaction to work --- without them, OpenCode uses default context sizes and will not compact before the effective limit.
|
|
360
|
+
|
|
361
|
+
Model limit changes require a service restart.
|
|
362
|
+
|
|
363
|
+
## Development
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
# Install with dev dependencies
|
|
367
|
+
uv sync --extra dev
|
|
368
|
+
|
|
369
|
+
# Run linter (covers src/, tests/, and operational scripts/)
|
|
370
|
+
uv run ruff check src/ tests/ scripts/
|
|
371
|
+
|
|
372
|
+
# Auto-fix lint issues
|
|
373
|
+
uv run ruff check --fix src/ tests/ scripts/
|
|
374
|
+
|
|
375
|
+
# Run formatter
|
|
376
|
+
uv run ruff format src/ tests/ scripts/
|
|
377
|
+
|
|
378
|
+
# Run type checker (covers src/ and scripts/)
|
|
379
|
+
uv run pyright src/ scripts/
|
|
380
|
+
|
|
381
|
+
# Run tests
|
|
382
|
+
uv run pytest
|
|
383
|
+
|
|
384
|
+
# Run tests with coverage
|
|
385
|
+
uv run coverage run -m pytest
|
|
386
|
+
uv run coverage report
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## Project Structure
|
|
390
|
+
|
|
391
|
+
```
|
|
392
|
+
src/eggpool/
|
|
393
|
+
├── __init__.py # Package version
|
|
394
|
+
├── __main__.py # python -m eggpool
|
|
395
|
+
├── app.py # FastAPI application factory
|
|
396
|
+
├── cli.py # Click CLI commands
|
|
397
|
+
├── auth.py # Local API key authentication
|
|
398
|
+
├── constants.py # Project-wide constants
|
|
399
|
+
├── errors.py # Exception hierarchy
|
|
400
|
+
├── logging.py # Structured logging setup
|
|
401
|
+
├── onboard.py # Interactive onboarding setup
|
|
402
|
+
├── models/
|
|
403
|
+
│ ├── config.py # Pydantic config models
|
|
404
|
+
│ ├── domain.py # Internal domain objects
|
|
405
|
+
│ ├── api.py # API response models
|
|
406
|
+
│ └── database.py # Database row models
|
|
407
|
+
├── db/
|
|
408
|
+
│ ├── connection.py # SQLite connection manager
|
|
409
|
+
│ ├── migrations.py # Schema migration runner
|
|
410
|
+
│ ├── repositories.py # Data access layer
|
|
411
|
+
│ └── schema/ # Ordered SQLite migrations + checksums
|
|
412
|
+
├── request/
|
|
413
|
+
│ ├── coordinator.py # Central request lifecycle orchestrator
|
|
414
|
+
│ ├── attempt_finalizer.py # Per-attempt terminal lifecycle
|
|
415
|
+
│ ├── finalizer.py # Idempotent request finalization
|
|
416
|
+
│ ├── body.py # Bounded request body reading
|
|
417
|
+
│ └── limits.py # Token estimation and context limit enforcement
|
|
418
|
+
├── accounts/ # Account registry and state
|
|
419
|
+
├── catalog/ # Model catalog, pricing, estimation, and protocols
|
|
420
|
+
├── routing/ # Quota-aware routing, eligibility, provider parsing
|
|
421
|
+
├── providers/ # ProviderClientPool, pproxy transport, connect CLI
|
|
422
|
+
├── proxy/ # Transparent proxy, streaming, and SSE observer
|
|
423
|
+
├── retry/ # Error classification and failover
|
|
424
|
+
├── health/ # Circuit breaker and health tracking
|
|
425
|
+
├── quota/ # Quota estimation, reservations, scoring
|
|
426
|
+
├── stats/ # Statistics queries and service
|
|
427
|
+
├── api/ # API endpoint handlers and error shaping
|
|
428
|
+
├── background/ # Background task supervisor and cleanup
|
|
429
|
+
├── dashboard/ # Self-updating server-rendered HTML dashboard
|
|
430
|
+
│ ├── render.py # HTML rendering functions
|
|
431
|
+
│ ├── routes.py # Dashboard HTTP routes
|
|
432
|
+
│ ├── theme.py # TOML theme to CSS variable translation
|
|
433
|
+
│ ├── escape.py # HTML escaping utilities
|
|
434
|
+
│ └── static/ # CSS, JavaScript, and favicon
|
|
435
|
+
├── integrations/ # External tool config generation (OpenCode, Claude Code)
|
|
436
|
+
├── security/ # Header redaction and security utilities
|
|
437
|
+
├── deploy/ # Bundled systemd/logrotate/cron snippets for CLI output
|
|
438
|
+
└── _share/ # Bundled config examples and assets for pipx installs
|
|
439
|
+
|
|
440
|
+
scripts/ # Operational scripts
|
|
441
|
+
├── install.sh # Quick install script
|
|
442
|
+
├── install_prompt.py # Installation prompt helper
|
|
443
|
+
├── check_database.py # Read-only database invariant checker
|
|
444
|
+
├── smoke_test.py # Deployment smoke test
|
|
445
|
+
└── verify_upstream_auth.py # Direct-upstream auth verifier
|
|
446
|
+
|
|
447
|
+
themes/ # 50+ Halloy-format .toml theme files
|
|
448
|
+
|
|
449
|
+
tests/
|
|
450
|
+
├── unit/ # Unit tests
|
|
451
|
+
├── integration/ # Integration tests (mocked upstreams)
|
|
452
|
+
├── contract/ # Contract tests (response format)
|
|
453
|
+
└── fixtures/ # Test data and schema baselines
|
|
454
|
+
|
|
455
|
+
docs/ # Documentation
|
|
456
|
+
├── deployment.md # Production deployment guide
|
|
457
|
+
├── raspberry-pi.md # Raspberry Pi setup guide
|
|
458
|
+
├── backup-restore.md # Backup and restore procedures
|
|
459
|
+
├── firewall.md # Firewall configuration
|
|
460
|
+
├── filesystem-layout.md # Filesystem layout reference
|
|
461
|
+
├── model-limits.md # Model context limit configuration
|
|
462
|
+
├── providers.md # Provider catalog and configuration guide
|
|
463
|
+
└── proxy.md # Per-account outbound proxy (pproxy)
|
|
464
|
+
|
|
465
|
+
config-examples/ # Editor-specific config snippets
|
|
466
|
+
├── opencode.jsonc # OpenCode provider config (JSONC)
|
|
467
|
+
└── claude-code.env # Claude Code environment variables
|
|
468
|
+
|
|
469
|
+
deploy/ # Deployment files
|
|
470
|
+
├── eggpool.service # systemd unit file
|
|
471
|
+
├── eggpool-logrotate.conf # Logrotate configuration
|
|
472
|
+
└── env.example # Example environment file
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
## Known Limitations
|
|
476
|
+
|
|
477
|
+
- Usage is proxy-observed; only traffic routed through the proxy is tracked.
|
|
478
|
+
- Weekly and monthly quota windows are rolling approximations unless providers expose authoritative subscription resets.
|
|
479
|
+
- Interrupted streams may not contain terminal usage data.
|
|
480
|
+
- Published prices may not perfectly match upstream subscription accounting.
|
|
481
|
+
- Context-tiered prices are conservatively estimated until pricing-rule support is added.
|
|
482
|
+
- Accounts used outside the proxy require manual offsets for accurate balancing.
|
|
483
|
+
- Model metadata and protocol behavior can change without notice.
|
|
484
|
+
- Both `/v1/chat/completions` (OpenAI) and `/v1/messages` (Anthropic) endpoints are required because mixed protocol catalogs resolve per-model.
|
|
485
|
+
- The dashboard and stats routes are public by default; set `dashboard.public = false` for authenticated access.
|
|
486
|
+
- LAN-only deployment reduces but does not eliminate security obligations.
|
|
487
|
+
- Configuration changes require service restart (live reload disabled for correctness).
|
|
488
|
+
|
|
489
|
+
## License
|
|
490
|
+
|
|
491
|
+
MIT
|
|
492
|
+
|
|
493
|
+
## Deployment
|
|
494
|
+
|
|
495
|
+
See `docs/deployment.md` for production deployment instructions.
|
|
496
|
+
|
|
497
|
+
For production (systemd):
|
|
498
|
+
|
|
499
|
+
```bash
|
|
500
|
+
sudo systemctl enable --now eggpool
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Configuration changes require a service restart; the unit
|
|
504
|
+
intentionally does not advertise any reload action:
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
sudo systemctl restart eggpool
|
|
508
|
+
sudo systemctl status eggpool
|
|
509
|
+
sudo journalctl -u eggpool -n 100 --no-pager
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
See [CHANGELOG](CHANGELOG.md) for release history.
|