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.
Files changed (166) hide show
  1. eggpool/__init__.py +5 -0
  2. eggpool/__main__.py +7 -0
  3. eggpool/_share/.env.example +9 -0
  4. eggpool/_share/config.example.toml +551 -0
  5. eggpool/accounts/__init__.py +1 -0
  6. eggpool/accounts/registry.py +181 -0
  7. eggpool/accounts/state.py +170 -0
  8. eggpool/api/__init__.py +1 -0
  9. eggpool/api/chat_completions.py +31 -0
  10. eggpool/api/errors.py +51 -0
  11. eggpool/api/messages.py +31 -0
  12. eggpool/api/models.py +76 -0
  13. eggpool/api/proxy_request.py +242 -0
  14. eggpool/api/stats.py +284 -0
  15. eggpool/app.py +893 -0
  16. eggpool/auth.py +92 -0
  17. eggpool/background/__init__.py +147 -0
  18. eggpool/background/cleanup.py +206 -0
  19. eggpool/catalog/__init__.py +1 -0
  20. eggpool/catalog/cache.py +645 -0
  21. eggpool/catalog/fetcher.py +188 -0
  22. eggpool/catalog/limits.py +306 -0
  23. eggpool/catalog/normalizer.py +119 -0
  24. eggpool/catalog/pricing.py +438 -0
  25. eggpool/catalog/protocols.py +194 -0
  26. eggpool/catalog/service.py +920 -0
  27. eggpool/cli.py +1481 -0
  28. eggpool/constants.py +34 -0
  29. eggpool/dashboard/__init__.py +3 -0
  30. eggpool/dashboard/_resources.py +14 -0
  31. eggpool/dashboard/escape.py +104 -0
  32. eggpool/dashboard/render.py +1448 -0
  33. eggpool/dashboard/routes.py +451 -0
  34. eggpool/dashboard/static/chart.umd.min.js +20 -0
  35. eggpool/dashboard/static/dashboard.css +341 -0
  36. eggpool/dashboard/static/favicon.svg +23 -0
  37. eggpool/dashboard/theme.py +514 -0
  38. eggpool/dashboard/themes/Booberry.toml +42 -0
  39. eggpool/dashboard/themes/Catppuccin Latte.toml +42 -0
  40. eggpool/dashboard/themes/Catppuccin Macchiato.toml +42 -0
  41. eggpool/dashboard/themes/Catppuccin Mocha.toml +42 -0
  42. eggpool/dashboard/themes/Cyber Red.toml +42 -0
  43. eggpool/dashboard/themes/Cyberpunk.toml +42 -0
  44. eggpool/dashboard/themes/Dark Green.toml +43 -0
  45. eggpool/dashboard/themes/Discord (80_ Saturation).toml +50 -0
  46. eggpool/dashboard/themes/Discord.toml +50 -0
  47. eggpool/dashboard/themes/Dracula.toml +42 -0
  48. eggpool/dashboard/themes/Ferra Light.toml +42 -0
  49. eggpool/dashboard/themes/Flexor Dark.toml +42 -0
  50. eggpool/dashboard/themes/Gruvbox.toml +42 -0
  51. eggpool/dashboard/themes/Halcyon Dark.toml +42 -0
  52. eggpool/dashboard/themes/IntelliJ Light.toml +42 -0
  53. eggpool/dashboard/themes/Kanagawa.toml +42 -0
  54. eggpool/dashboard/themes/Macaw Dark.toml +42 -0
  55. eggpool/dashboard/themes/Macaw Light.toml +42 -0
  56. eggpool/dashboard/themes/Matrix.toml +42 -0
  57. eggpool/dashboard/themes/Noctis Lilac.toml +42 -0
  58. eggpool/dashboard/themes/Nord.toml +42 -0
  59. eggpool/dashboard/themes/Nostromo Terminal.toml +42 -0
  60. eggpool/dashboard/themes/One Dark.toml +42 -0
  61. eggpool/dashboard/themes/Oxocarbon.toml +42 -0
  62. eggpool/dashboard/themes/Rose Pine Dawn.toml +42 -0
  63. eggpool/dashboard/themes/Rose Pine Moon.toml +42 -0
  64. eggpool/dashboard/themes/Rose Pine.toml +42 -0
  65. eggpool/dashboard/themes/Solarized Dark.toml +42 -0
  66. eggpool/dashboard/themes/Sonokai.toml +42 -0
  67. eggpool/dashboard/themes/Tokyo Night Storm.toml +42 -0
  68. eggpool/dashboard/themes/VESPER.toml +42 -0
  69. eggpool/dashboard/themes/Zenburn.toml +42 -0
  70. eggpool/dashboard/themes/acton.toml +42 -0
  71. eggpool/dashboard/themes/bam.toml +42 -0
  72. eggpool/dashboard/themes/base16-atelier-forest-light.toml +45 -0
  73. eggpool/dashboard/themes/berlin.toml +42 -0
  74. eggpool/dashboard/themes/black but with important highlights.toml +42 -0
  75. eggpool/dashboard/themes/broc.toml +42 -0
  76. eggpool/dashboard/themes/cork.toml +42 -0
  77. eggpool/dashboard/themes/ferra.toml +42 -0
  78. eggpool/dashboard/themes/forest.toml +42 -0
  79. eggpool/dashboard/themes/lisbon.toml +42 -0
  80. eggpool/dashboard/themes/midnight.toml +42 -0
  81. eggpool/dashboard/themes/oslo.toml +42 -0
  82. eggpool/dashboard/themes/plum.toml +43 -0
  83. eggpool/dashboard/themes/portland.toml +42 -0
  84. eggpool/dashboard/themes/sunset.toml +42 -0
  85. eggpool/dashboard/themes/tofino.toml +42 -0
  86. eggpool/dashboard/themes/vanimo.toml +42 -0
  87. eggpool/dashboard/themes/vik.toml +42 -0
  88. eggpool/db/__init__.py +0 -0
  89. eggpool/db/connection.py +401 -0
  90. eggpool/db/migrations.py +125 -0
  91. eggpool/db/repositories.py +989 -0
  92. eggpool/db/schema/0001_initial.sql +78 -0
  93. eggpool/db/schema/0002_indexes.sql +24 -0
  94. eggpool/db/schema/0003_request_attempts.sql +20 -0
  95. eggpool/db/schema/0004_integration_hardening.sql +34 -0
  96. eggpool/db/schema/0005_price_microdollars.sql +15 -0
  97. eggpool/db/schema/0006_correct_price_microdollars.sql +12 -0
  98. eggpool/db/schema/0007_price_cache_rates.sql +5 -0
  99. eggpool/db/schema/0008_proxy_request_identity.sql +9 -0
  100. eggpool/db/schema/0009_model_protocol_source.sql +3 -0
  101. eggpool/db/schema/0010_health_probe.sql +5 -0
  102. eggpool/db/schema/0011_model_resolution_status.sql +1 -0
  103. eggpool/db/schema/0012_drop_reservations_estimated_microdollars.sql +6 -0
  104. eggpool/db/schema/0013_request_attempts_account_id_index.sql +7 -0
  105. eggpool/db/schema/0014_bandwidth_tracking.sql +2 -0
  106. eggpool/db/schema/0015_multi_provider.sql +23 -0
  107. eggpool/db/schema/0016_requests_provider_id.sql +2 -0
  108. eggpool/db/schema/0017_price_snapshots_provider_id.sql +4 -0
  109. eggpool/db/schema/0018_provider_pings.sql +16 -0
  110. eggpool/db/schema/0019_client_ip.sql +4 -0
  111. eggpool/db/schema/0020_performance_indexes.sql +19 -0
  112. eggpool/db/schema/0021_provider_model_metadata.sql +19 -0
  113. eggpool/db/schema/0022_dashboard_indexes.sql +14 -0
  114. eggpool/db/schema/checksums.json +26 -0
  115. eggpool/deploy/__init__.py +126 -0
  116. eggpool/errors.py +123 -0
  117. eggpool/health/__init__.py +8 -0
  118. eggpool/health/circuit_breaker.py +146 -0
  119. eggpool/health/health_manager.py +339 -0
  120. eggpool/integrations/__init__.py +1 -0
  121. eggpool/integrations/opencode.py +90 -0
  122. eggpool/logging.py +50 -0
  123. eggpool/models/__init__.py +0 -0
  124. eggpool/models/api.py +32 -0
  125. eggpool/models/config.py +658 -0
  126. eggpool/models/database.py +99 -0
  127. eggpool/models/domain.py +59 -0
  128. eggpool/onboard.py +111 -0
  129. eggpool/providers/__init__.py +0 -0
  130. eggpool/providers/_templates.toml +574 -0
  131. eggpool/providers/client_pool.py +131 -0
  132. eggpool/providers/connect.py +988 -0
  133. eggpool/providers/contract.py +91 -0
  134. eggpool/providers/pproxy_transport.py +293 -0
  135. eggpool/proxy/__init__.py +1 -0
  136. eggpool/proxy/client.py +140 -0
  137. eggpool/proxy/sse_observer.py +283 -0
  138. eggpool/proxy/usage.py +114 -0
  139. eggpool/py.typed +1 -0
  140. eggpool/quota/__init__.py +13 -0
  141. eggpool/quota/estimation.py +639 -0
  142. eggpool/quota/reservation.py +193 -0
  143. eggpool/quota/scorer.py +215 -0
  144. eggpool/request/__init__.py +13 -0
  145. eggpool/request/attempt_finalizer.py +125 -0
  146. eggpool/request/body.py +70 -0
  147. eggpool/request/coordinator.py +1638 -0
  148. eggpool/request/finalizer.py +392 -0
  149. eggpool/request/limits.py +152 -0
  150. eggpool/retry/__init__.py +7 -0
  151. eggpool/retry/classification.py +207 -0
  152. eggpool/routing/__init__.py +1 -0
  153. eggpool/routing/eligibility.py +95 -0
  154. eggpool/routing/provider.py +20 -0
  155. eggpool/routing/router.py +395 -0
  156. eggpool/security/__init__.py +7 -0
  157. eggpool/security/redaction.py +327 -0
  158. eggpool/stats/__init__.py +37 -0
  159. eggpool/stats/queries.py +598 -0
  160. eggpool/stats/service.py +548 -0
  161. eggpool/toml_edit.py +101 -0
  162. eggpool-0.1.0.dist-info/METADATA +512 -0
  163. eggpool-0.1.0.dist-info/RECORD +166 -0
  164. eggpool-0.1.0.dist-info/WHEEL +4 -0
  165. eggpool-0.1.0.dist-info/entry_points.txt +2 -0
  166. 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
+ [![PyPI version](https://badge.fury.io/py/eggpool.svg)](https://pypi.org/project/eggpool/)
45
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://www.python.org/)
46
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
47
+ [![CI](https://github.com/eggstack/eggpool/actions/workflows/ci.yml/badge.svg)](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.