eggpool 0.2.2__tar.gz → 0.3.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.
- eggpool-0.3.0/AGENTS.md +203 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/CHANGELOG.md +99 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/PKG-INFO +351 -85
- eggpool-0.3.0/README.md +783 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/architecture/README.md +158 -9
- {eggpool-0.2.2 → eggpool-0.3.0}/config.example.toml +64 -16
- eggpool-0.3.0/docs/deployment.md +620 -0
- eggpool-0.3.0/docs/filesystem-layout.md +91 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/docs/providers.md +133 -3
- {eggpool-0.2.2 → eggpool-0.3.0}/docs/raspberry-pi.md +21 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/pyproject.toml +1 -1
- {eggpool-0.2.2 → eggpool-0.3.0}/scripts/check_database.py +1 -1
- eggpool-0.3.0/scripts/install.sh +317 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/scripts/verify_upstream_auth.py +389 -185
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/__main__.py +3 -1
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/_share/config.example.toml +98 -16
- eggpool-0.3.0/src/eggpool/api/backoff.py +117 -0
- eggpool-0.3.0/src/eggpool/api/runtime.py +52 -0
- eggpool-0.3.0/src/eggpool/api/stats.py +596 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/app.py +342 -38
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/background/__init__.py +72 -1
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/background/cleanup.py +9 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/catalog/cache.py +44 -0
- eggpool-0.3.0/src/eggpool/catalog/catalog_resolvers.py +352 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/catalog/pricing.py +164 -50
- eggpool-0.3.0/src/eggpool/catalog/pricing_aliases.py +247 -0
- eggpool-0.3.0/src/eggpool/catalog/pricing_resolver.py +326 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/catalog/service.py +196 -109
- eggpool-0.3.0/src/eggpool/cli.py +74 -0
- eggpool-0.2.2/src/eggpool/cli.py → eggpool-0.3.0/src/eggpool/cli_full.py +1723 -265
- eggpool-0.3.0/src/eggpool/constants.py +89 -0
- eggpool-0.3.0/src/eggpool/cost_recompute.py +155 -0
- eggpool-0.3.0/src/eggpool/dashboard/escape.py +283 -0
- eggpool-0.3.0/src/eggpool/dashboard/render.py +3419 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/routes.py +265 -9
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/static/dashboard.css +184 -0
- eggpool-0.3.0/src/eggpool/dashboard/static/dashboard.js +476 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/connection.py +48 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/repositories.py +496 -26
- eggpool-0.3.0/src/eggpool/db/schema/0024_account_backoffs.sql +31 -0
- eggpool-0.3.0/src/eggpool/db/schema/0025_stale_request_index.sql +18 -0
- eggpool-0.3.0/src/eggpool/db/schema/0026_attempt_observability.sql +76 -0
- eggpool-0.3.0/src/eggpool/db/schema/0027_routing_decisions.sql +52 -0
- eggpool-0.3.0/src/eggpool/db/schema/0028_operational_events.sql +32 -0
- eggpool-0.3.0/src/eggpool/db/schema/0029_latency_phases.sql +29 -0
- eggpool-0.3.0/src/eggpool/db/schema/0030_model_pricing_aliases.sql +32 -0
- eggpool-0.3.0/src/eggpool/db/schema/0031_price_snapshot_provenance.sql +21 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/checksums.json +9 -1
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/deploy/__init__.py +154 -4
- eggpool-0.3.0/src/eggpool/deploy_user.py +324 -0
- eggpool-0.3.0/src/eggpool/fastcli.py +169 -0
- eggpool-0.3.0/src/eggpool/health/backoff.py +253 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/health/health_manager.py +115 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/lifecycle/uninstall.py +183 -42
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/models/config.py +117 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/onboard.py +15 -15
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/providers/_templates.toml +86 -16
- eggpool-0.3.0/src/eggpool/quota/audit.py +182 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/request/attempt_finalizer.py +13 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/request/coordinator.py +473 -28
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/request/finalizer.py +7 -10
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/retry/classification.py +134 -17
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/routing/eligibility.py +22 -9
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/routing/router.py +49 -0
- eggpool-0.3.0/src/eggpool/runtime.py +395 -0
- eggpool-0.3.0/src/eggpool/runtime_metrics.py +544 -0
- eggpool-0.3.0/src/eggpool/runtime_paths.py +118 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/stats/__init__.py +18 -0
- eggpool-0.3.0/src/eggpool/stats/queries.py +1869 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/stats/service.py +441 -1
- eggpool-0.2.2/AGENTS.md +0 -120
- eggpool-0.2.2/README.md +0 -517
- eggpool-0.2.2/docs/deployment.md +0 -247
- eggpool-0.2.2/docs/filesystem-layout.md +0 -43
- eggpool-0.2.2/scripts/install.sh +0 -182
- eggpool-0.2.2/src/eggpool/api/stats.py +0 -278
- eggpool-0.2.2/src/eggpool/constants.py +0 -40
- eggpool-0.2.2/src/eggpool/dashboard/escape.py +0 -104
- eggpool-0.2.2/src/eggpool/dashboard/render.py +0 -1448
- eggpool-0.2.2/src/eggpool/runtime.py +0 -163
- eggpool-0.2.2/src/eggpool/stats/queries.py +0 -616
- {eggpool-0.2.2 → eggpool-0.3.0}/.env.example +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/.github/workflows/ci.yml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/.github/workflows/release.yml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/.gitignore +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/LICENSE +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/config-examples/claude-code.env +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/config-examples/opencode.jsonc +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/deploy/eggpool-logrotate.conf +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/deploy/eggpool.service +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/deploy/env.example +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/docs/backup-restore.md +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/docs/firewall.md +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/docs/model-limits.md +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/docs/proxy.md +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/scripts/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/scripts/install_prompt.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/scripts/smoke_test.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/_share/.env.example +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/accounts/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/accounts/registry.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/accounts/state.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/api/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/api/chat_completions.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/api/errors.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/api/messages.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/api/models.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/api/proxy_request.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/auth.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/catalog/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/catalog/fetcher.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/catalog/limits.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/catalog/normalizer.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/catalog/protocols.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/config.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/_resources.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/static/chart.umd.min.js +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/static/favicon.svg +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/theme.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Booberry.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Catppuccin Latte.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Catppuccin Macchiato.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Catppuccin Mocha.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Cyber Red.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Cyberpunk.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Dark Green.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Discord (80_ Saturation).toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Discord.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Dracula.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Ferra Light.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Flexor Dark.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Gruvbox.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Halcyon Dark.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/IntelliJ Light.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Kanagawa.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Macaw Dark.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Macaw Light.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Matrix.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Noctis Lilac.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Nord.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Nostromo Terminal.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/One Dark.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Oxocarbon.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Rose Pine Dawn.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Rose Pine Moon.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Rose Pine.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Solarized Dark.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Sonokai.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Tokyo Night Storm.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/VESPER.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/Zenburn.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/acton.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/bam.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/base16-atelier-forest-light.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/berlin.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/black but with important highlights.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/broc.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/cork.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/ferra.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/forest.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/lisbon.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/midnight.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/oslo.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/plum.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/portland.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/sunset.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/tofino.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/vanimo.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/dashboard/themes/vik.toml +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/migrations.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0001_initial.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0002_indexes.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0003_request_attempts.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0004_integration_hardening.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0005_price_microdollars.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0006_correct_price_microdollars.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0007_price_cache_rates.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0008_proxy_request_identity.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0009_model_protocol_source.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0010_health_probe.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0011_model_resolution_status.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0012_drop_reservations_estimated_microdollars.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0013_request_attempts_account_id_index.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0014_bandwidth_tracking.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0015_multi_provider.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0016_requests_provider_id.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0017_price_snapshots_provider_id.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0018_provider_pings.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0019_client_ip.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0020_performance_indexes.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0021_provider_model_metadata.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0022_dashboard_indexes.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/db/schema/0023_deprecated_model_placeholder.sql +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/errors.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/health/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/health/circuit_breaker.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/integrations/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/integrations/opencode.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/lifecycle/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/lifecycle/backup.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/logging.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/models/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/models/api.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/models/database.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/models/domain.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/providers/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/providers/auth.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/providers/client_pool.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/providers/connect.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/providers/contract.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/providers/pproxy_transport.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/proxy/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/proxy/client.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/proxy/sse_observer.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/proxy/usage.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/py.typed +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/quota/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/quota/estimation.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/quota/reservation.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/quota/scorer.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/request/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/request/body.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/request/limits.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/retry/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/routing/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/routing/provider.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/security/__init__.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/security/redaction.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/src/eggpool/toml_edit.py +0 -0
- {eggpool-0.2.2 → eggpool-0.3.0}/uv.lock +0 -0
eggpool-0.3.0/AGENTS.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
## Skills
|
|
4
|
+
|
|
5
|
+
Project-specific skills are in `.opencode/skills/`:
|
|
6
|
+
|
|
7
|
+
- `architecture` — design principles, request lifecycle, invariants, error hierarchy
|
|
8
|
+
- `deployment` — production deployment, systemd, operational scripts
|
|
9
|
+
- `development` — linting, testing, pre-commit checks, code style
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
- Package manager: **uv** (not pip). Install deps: `uv sync --extra dev`
|
|
14
|
+
- Entry point: `src/eggpool/cli.py` → `eggpool` console script
|
|
15
|
+
- Config: `config.toml` + `.env` for API keys
|
|
16
|
+
|
|
17
|
+
## Pre-commit Checks (run before every commit)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv run ruff format --check src/ tests/ scripts/
|
|
21
|
+
uv run ruff check src/ tests/ scripts/
|
|
22
|
+
uv run pyright src/ scripts/
|
|
23
|
+
uv run pytest
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
All four must pass with zero errors.
|
|
27
|
+
|
|
28
|
+
## Code Style
|
|
29
|
+
|
|
30
|
+
- Python 3.11+ with `from __future__ import annotations` in ALL files
|
|
31
|
+
- Type hints on all function signatures and return types
|
|
32
|
+
- Ruff: E, F, W, I, N, UP, B, A, SIM, TCH rules
|
|
33
|
+
- Pyright strict mode — covers `src/` AND `scripts/` (not tests)
|
|
34
|
+
- Line length: 88 chars
|
|
35
|
+
- Use `NoReturn` for functions that never return (e.g., `sys.exit`)
|
|
36
|
+
|
|
37
|
+
## Testing
|
|
38
|
+
|
|
39
|
+
- pytest with `asyncio_mode = "strict"` (from `pyproject.toml`)
|
|
40
|
+
- respx for HTTPX upstream mocking
|
|
41
|
+
- Tests in `tests/unit/`, `tests/integration/`, `tests/contract/`
|
|
42
|
+
- Provider contract tests: `uv run pytest tests/unit/test_contract.py tests/unit/test_contract_urls.py -v`
|
|
43
|
+
|
|
44
|
+
## File Organization
|
|
45
|
+
|
|
46
|
+
- Source: `src/eggpool/`
|
|
47
|
+
- Tests: `tests/` (mirrors src structure)
|
|
48
|
+
- Config: `config.example.toml`, `.env.example`
|
|
49
|
+
- DB schema: `src/eggpool/db/schema/`
|
|
50
|
+
- Scripts: `scripts/` (operational, also type-checked by pyright)
|
|
51
|
+
- Deployment: `deploy/`
|
|
52
|
+
|
|
53
|
+
## Multi-Provider Architecture
|
|
54
|
+
|
|
55
|
+
- Provider-suffixed model IDs: `model-id/provider-id` (e.g., `claude-sonnet-4/opencode-go`)
|
|
56
|
+
- `ProviderClientPool` manages per-provider `httpx.AsyncClient` with independent connection pools
|
|
57
|
+
- Flat `[[accounts]]` configs auto-normalize to a default `opencode-go` provider
|
|
58
|
+
- `parse_model_provider()` and `format_model_provider()` in `routing/provider.py`
|
|
59
|
+
|
|
60
|
+
## Provider Contracts
|
|
61
|
+
|
|
62
|
+
Key design rules that are easy to get wrong:
|
|
63
|
+
|
|
64
|
+
- `compose_provider_url()` is the single source of truth for upstream URLs — catalog fetch, non-streaming chat, and streaming chat all call it
|
|
65
|
+
- `build_auth_headers()` reads from `ProviderConfig.auth` — never hardcode Bearer
|
|
66
|
+
- `base_url` ending `/v1` + path beginning `/v1/` is rejected (duplicate version prefix)
|
|
67
|
+
- `auth.mode = "none"` sends no upstream auth (Ollama local)
|
|
68
|
+
- All outbound dispatch paths use `compose_provider_url()` so providers cannot list at one host and dispatch to another
|
|
69
|
+
- **`models_endpoint.method = "DISABLED"`** skips live model listing. Providers using this must declare `[[providers.<id>.static_models]]` rows; otherwise `eggpool check-config` warns and the catalog will be empty for the provider
|
|
70
|
+
- **`static_models` lifecycle**: rows are seeded by `CatalogService._seed_static_models` BEFORE live fetch tasks run. Static-source fields (`protocol`, `protocol_source == "static_config"`, `supports_tools`, `supports_vision`) are preserved by `ModelCatalogCache._preserve_static_fields` when live rows arrive without them
|
|
71
|
+
- **`eggpool check-config`** runs `_check_stale_contracts` after a successful load and emits advisory warnings for: `DISABLED` without static seeds, `DISABLED` with `require_models = true`, declared path fields ignored by protocol mismatch, duplicate `/v1` segments, `Authorization` in static headers when `auth.mode != "none"`, legacy `models_method`/`models_path` fields, and Anthropic providers using `auth.header = "Authorization"` instead of `x-api-key`. Warnings do not change the exit code
|
|
72
|
+
|
|
73
|
+
## Bearer-prefix Guard
|
|
74
|
+
|
|
75
|
+
`AppConfig.validate_account_credentials()` rejects API keys beginning with `Bearer` for `auth.mode = "bearer"` providers. EggPool prepends `Bearer ` automatically; storing `Bearer <token>` produces `Authorization: Bearer Bearer <token>` and causes 401s. Providers using `auth.mode = "raw_authorization"` are unaffected.
|
|
76
|
+
|
|
77
|
+
## Error Handling
|
|
78
|
+
|
|
79
|
+
Use the hierarchy in `errors.py`. Chain exceptions with `raise ... from err` or `raise ... from None`.
|
|
80
|
+
|
|
81
|
+
- `AggregatorError` → `ConfigError`, `DatabaseError`, `ProxyError`
|
|
82
|
+
- `UpstreamError` (has `status_code`) → `TemporaryUpstreamError`, `TransientUpstreamError`, `AuthenticationError`, `QuotaExhaustedError`, `RateLimitError` (has `retry_after`), `ModelUnavailableError`
|
|
83
|
+
- `ModelNotFoundError` (has `model_id`), `NoEligibleAccountError`, `CatalogUnavailableError`, `AuthenticationUnavailableError`, `UpstreamExhaustedError`, `AccountSuspendedError`, `RequestTooLargeError`, `ContextLimitExceededError`
|
|
84
|
+
|
|
85
|
+
## Gotchas
|
|
86
|
+
|
|
87
|
+
- Configuration changes require a service restart; live reload is intentionally not supported
|
|
88
|
+
- No CI workflows or pre-commit hooks are configured in this repo
|
|
89
|
+
- `Database.vacuum()` is the only sanctioned path for `VACUUM` in production code
|
|
90
|
+
- Every DML write must run inside `async with db.transaction():`
|
|
91
|
+
- SQLite transactions are serialized across concurrent tasks via a single connection lock + ContextVar
|
|
92
|
+
- Requests must be persisted before upstream dispatch; pre-body failures can retry, but no retry after first downstream byte
|
|
93
|
+
- **Upstream-authoritative suppression**: local quota estimates are advisory by default (`local_quota_mode = "score_only"`). Above-capacity accounts stay eligible; only upstream-observed failures (429/402/5xx/auth) and explicit operator disablement suppress routing. Switch to `hard_cap` only as an opt-in escape hatch.
|
|
94
|
+
- **Backoff persistence**: upstream-derived backoffs survive restarts via the `account_backoffs` table (`src/eggpool/db/schema/0024_account_backoffs.sql`). Hydration runs at startup after account sync, best-effort (never blocks boot). Local cost overruns must never be persisted as backoff rows.
|
|
95
|
+
- **Synthetic 503 vs 502**: `ModelUnavailableError` (503) is reserved for genuine pre-dispatch unavailability. `UpstreamExhaustedError` (502) is raised when every candidate account was attempted and exhausted mid-request. Single-account upstream errors pass through to the client rather than becoming synthetic 503s.
|
|
96
|
+
- **Streaming finalizer shielding**: streaming `_build_stream_generator` finalization runs under `asyncio.shield(asyncio.wait_for(..., timeout=10))` so ASGI task cancellation cannot kill the finalizer while it holds the DB lock. Leaks that escape this path are caught by the periodic `stale_request_finalizer` background task (`app._finalize_stale_requests`, runs every 60s) which force-finalizes any request that has been `pending` longer than `upstream.read_timeout_s`.
|
|
97
|
+
- **Startup crash recovery**: `_crash_recovery` runs at every startup and recovers ALL pending requests and ALL active reservations with no time threshold. A process restart is a definitive boundary, so leaked state from the previous process is unconditionally cleaned up. If `Crash recovery: marked N stale requests` appears in logs, the safety net caught leaks from the previous run.
|
|
98
|
+
- **Pricing resolution pipeline**: prices flow TOML override → upstream metadata → external catalog (OpenRouter / OpenCode Zen via the alias registry). `ResolvedPricing` records `source_detail` (`operator_override` / `provider_metadata` / `openrouter` / `opencode_zen`) and `source_confidence` (`exact_external_id` / `curated_alias` / `provider_metadata`). Cost rows are then labelled `derived` (every category trusted), `partial` (some categories filled by per-category fallback), `estimated` (no trusted rates), or `unknown` (no token usage). `partial_count` is a new exactness value exposed via `/api/stats/summary`, `/api/stats/accounts`, and `/api/stats/models`. The dashboard renders a per-row cost-exactness badge and a high-spend estimated warning banner (>$10 estimated) on the Accounts page.
|
|
99
|
+
- **`eggpool stats recompute-costs [--dry-run|--apply] [--limit N]`**: walks the requests table in started_at DESC order, recomputes cost from the current price snapshots, and reports / applies the change. Default is `--dry-run`. Use after upgrading the resolver to fix inflated totals on cached-token-heavy models (e.g. MiMo 2.5). Implemented in `src/eggpool/cost_recompute.py` and reuses the live `CostCalculator` so the new values match what the finalizer would write today.
|
|
100
|
+
- **Migration 0030 (`model_pricing_aliases`) + 0031 (`price_snapshot_provenance`)**: 0030 introduces the alias registry that maps upstream model IDs (e.g. `mimo-v2.5`) onto external catalog IDs (e.g. `xiaomi/mimo-v2.5`) with an `exact`/`curated_alias`/`ambiguous_skip` confidence enum. 0031 adds `source_detail`, `source_confidence`, `catalog_source` columns to `model_price_snapshots` so the dashboard can attribute prices back to the resolver that produced them. Seed data lives in `seed_default_aliases()` (`src/eggpool/catalog/pricing_aliases.py`) and runs idempotently at startup via `CatalogService.attach_pricing_resolvers()`.
|
|
101
|
+
|
|
102
|
+
## Observability
|
|
103
|
+
|
|
104
|
+
- **Attempt analytics**: per-attempt aggregates including latency percentiles, byte totals, retry rate, and the `retry_category` distribution. Every `request_attempts` row carries `provider_id/model_id/protocol/retry_category/release_reason/bytes_received/latency_ms/streamed/is_retry_outcome`
|
|
105
|
+
- **Routing analytics**: per-`(model, provider)` decision aggregates, account-level selection counts, and per-`(account, reason)` exclusion counts. Every routing decision is persisted as a `routing_decisions` row inside the same transaction as the `request_attempts` INSERT
|
|
106
|
+
- **Latency phases**: decomposes each request into `upstream_connect_ms`, `upstream_read_ms`, and `coordinator_overhead_ms`
|
|
107
|
+
- **Operational health**: `crash_recovery`, `stale_request_finalizer`, and `reservation_reconcile` safety-net events are recorded as `operational_events` rows in the same transaction as the durable state mutation
|
|
108
|
+
- **Pending health**: instantaneous snapshot of pending request count, oldest pending age, stale-pending count (>15 min), active reservations, total reserved microdollars, and oldest reservation age. Auth-gated
|
|
109
|
+
- **Per-request trace**: parent request row, full attempt chain, and per-attempt routing decisions. Returns account name, model, protocol, status, error class (never raw error_detail), and timing. Auth-gated
|
|
110
|
+
- **Recent request metadata**: bounded list of recent request rows with metadata only (no body, no auth headers, no error_detail). Auth-gated
|
|
111
|
+
- **Cost/cache/reasoning exactness**: per-account and per-model `exact_count`, `partial_count`, `derived_count`, `estimated_count`, `cache_read_ratio`, `cache_write_ratio`, `reasoning_output_ratio`
|
|
112
|
+
- Full API surface is documented in the `architecture` skill
|
|
113
|
+
|
|
114
|
+
## Dashboard
|
|
115
|
+
|
|
116
|
+
- Server-rendered HTML pages in `src/eggpool/dashboard/render.py`
|
|
117
|
+
- Overview page auto-refreshes in place (every `[dashboard].refresh_interval_s`); all other pages are static
|
|
118
|
+
- Charts use bundled Chart.js v4 at `/static/chart.js` with `Cache-Control: public, max-age=86400`
|
|
119
|
+
- New pages opt into Chart.js via `include_chart_js=True` in `_render_layout`
|
|
120
|
+
- Frontend helpers in `src/eggpool/dashboard/static/dashboard.js` under `window.EggPoolDashboard`
|
|
121
|
+
- Full page list and chart lifecycle details are in the `architecture` skill
|
|
122
|
+
|
|
123
|
+
## Fast-Path CLI
|
|
124
|
+
|
|
125
|
+
- `src/eggpool/cli.py` is a tiny bootstrap (74 lines)
|
|
126
|
+
- `main()` calls `eggpool.fastcli.maybe_run_fast_command()` first; recognized fast commands (`croncheck`, `ensure-running`) are dispatched without importing Click
|
|
127
|
+
- **Do not add transitive imports to `runtime_paths` or `fastcli`** — they are stdlib-only and must stay lightweight for the Raspberry Pi watchdog contract
|
|
128
|
+
- Unrecognized commands fall through to `eggpool.cli_full`, which holds the heavy Click CLI
|
|
129
|
+
- Public symbols (`cli`, helpers used by tests) are lazily forwarded from `cli_full` via PEP 562 `__getattr__` — so `from eggpool.cli import cli` and existing test imports still work without loading the full graph
|
|
130
|
+
- See `plans/lightweight-cli-watchdog.md` for the full design
|
|
131
|
+
|
|
132
|
+
## Process Model
|
|
133
|
+
|
|
134
|
+
- `eggpool serve` runs as a single supervisor process that invokes `Granian` with `workers=1`; Granian spawns one worker process, so exactly **two** processes appear under the canonical name
|
|
135
|
+
- The Granian worker is launched with `process_name="eggpool"`, so `ps` / `top` / `pgrep` show the canonical name for both supervisor and worker (not a generic `python` entry)
|
|
136
|
+
- `[server].threads` (int, default `1`, min `1`, max `64`) controls Granian `runtime_threads` — the number of worker event-loop threads. Default is `1` for SBC / Raspberry Pi; raise on capable hardware
|
|
137
|
+
- PID path resolution lives in `eggpool.runtime_paths` and is the single source of truth (`default_pid_file()`). Precedence: `$EGGPOOL_PID_FILE` → `$XDG_RUNTIME_DIR/eggpool.pid` → `~/.local/state/eggpool/eggpool.pid` → `/tmp/eggpool-<UID>.pid`. The PID file is owned by the **supervisor**, written before `Granian.serve()` and cleared in a `finally` block. The FastAPI lifespan no longer touches the PID file
|
|
138
|
+
- `eggpool serve` refuses to start a second instance: first checks `runtime.read_pid()` + `runtime.is_process_running()`; if no live PID, probes `GET /v1/healthz` via stdlib `urllib.request` (bind `0.0.0.0` / `::` is rewritten to `127.0.0.1`). A live PID or a 200 from the probe exits `1`. Stale PID files (PID not running) are cleared before starting
|
|
139
|
+
- `eggpool restart` delegates to `runtime.restart_server` which calls `runtime.send_sigterm` and `runtime.start_server` (which `subprocess.Popen`s a new supervisor)
|
|
140
|
+
- `eggpool ensure-running` is the canonical cron watchdog command — it atomically checks-and-starts without ever spawning a duplicate instance. Use it from `@reboot` and `*/5 * * * *` crontab lines, not `croncheck || eggpool serve &`
|
|
141
|
+
|
|
142
|
+
### Daemon Mode
|
|
143
|
+
|
|
144
|
+
- Foreground `eggpool serve` remains the debugging/operator path and prints Granian logs to the calling terminal
|
|
145
|
+
- `eggpool serve --daemon` validates the config, refuses to start a second instance, then spawns a detached child and returns promptly with a short success message pointing at the log file
|
|
146
|
+
- The detached child runs the normal foreground `serve` command (Granian supervisor + worker). The `--daemon` flag is **never** forwarded to the child; detachment is purely a parent-side concern. The child owns its own PID file lifecycle via `runtime.write_pid_file()` / `runtime.clear_pid_file()`
|
|
147
|
+
- Default log destination is `~/.local/state/eggpool/eggpool.log`, resolvable via `eggpool.runtime_paths.default_log_file()`. Override with `--log-file PATH` or `$EGGPOOL_LOG_FILE`. A log file beats `/dev/null` by default because a silent background failure is hard to diagnose
|
|
148
|
+
- The child is launched with `start_new_session=True`, `stdin=subprocess.DEVNULL`, and `stdout`/`stderr` redirected to the log file (or `/dev/null` when `--quiet` is set without `--log-file`). The child survives shell exit and signals to the parent CLI do not propagate
|
|
149
|
+
- `runtime.start_server()` signature: `start_server(config_path, *, cwd=None, daemon=True, log_path=None, quiet=True, verify=False, verify_timeout_s=3.0)`. `runtime.restart_server()` accepts the same `daemon`, `log_path`, `quiet` options
|
|
150
|
+
- `serve --daemon` refuses to daemonize when the effective UID is 0 unless `--as-root` is passed (prevents accidental root personal deployment)
|
|
151
|
+
- Systemd should **not** use `--daemon`. The systemd unit already owns the process lifecycle; run foreground `serve` and let systemd manage the PID, journal logs, and restart policy
|
|
152
|
+
|
|
153
|
+
## CLI Commands
|
|
154
|
+
|
|
155
|
+
| Command | Description |
|
|
156
|
+
|---------|-------------|
|
|
157
|
+
| `eggpool help` | Show help message and available commands |
|
|
158
|
+
| `eggpool version` | Print the installed version |
|
|
159
|
+
| `eggpool serve` | Start the aggregation proxy server (default command). Flags: `--daemon`, `--log-file PATH`, `--quiet`, `--as-root` |
|
|
160
|
+
| `eggpool check-config` | Validate the configuration file |
|
|
161
|
+
| `eggpool migrate` | Run database migrations |
|
|
162
|
+
| `eggpool onboard` | Run the interactive onboarding setup (connect providers, start server) |
|
|
163
|
+
| `eggpool connect` | Connect to a new provider interactively |
|
|
164
|
+
| `eggpool connect list` | List available providers |
|
|
165
|
+
| `eggpool logout` | Remove a configured provider account |
|
|
166
|
+
| `eggpool rehash` | Restart to apply config changes |
|
|
167
|
+
| `eggpool restart` | Fully restart the server (stop then start) |
|
|
168
|
+
| `eggpool stop` | Stop the running server |
|
|
169
|
+
| `eggpool set` | Set a server configuration value and restart |
|
|
170
|
+
| `eggpool getkey` | Print the current server API key |
|
|
171
|
+
| `eggpool newkey` | Generate a new server API key |
|
|
172
|
+
| `eggpool edit` | Open the configuration file in the default editor |
|
|
173
|
+
| `eggpool configsetup` | Print configuration snippets for code editors |
|
|
174
|
+
| `eggpool configsetup opencode` | Print OpenCode provider config JSON with model limits |
|
|
175
|
+
| `eggpool configsetup claude-code` | Print Claude Code config snippet |
|
|
176
|
+
| `eggpool update` | Check for updates and reinstall if newer |
|
|
177
|
+
| `eggpool croncheck` | Lightweight check: exit 0 if server is running, exit 1 if not |
|
|
178
|
+
| `eggpool ensure-running` | Repair: start the server if it is not running; no-op when alive. Fast-path. |
|
|
179
|
+
| `eggpool runtime-status` | Print compact runtime health summary from running server |
|
|
180
|
+
| `eggpool models refresh` | Refresh model catalog from upstream |
|
|
181
|
+
| `eggpool accounts status` | Show configured account status |
|
|
182
|
+
| `eggpool accounts list` | List configured provider accounts |
|
|
183
|
+
| `eggpool dashboard public` | Toggle dashboard public access |
|
|
184
|
+
| `eggpool db vacuum` | Reclaim SQLite space |
|
|
185
|
+
| `eggpool init-config` | Write bundled config.example.toml to current directory or TARGET |
|
|
186
|
+
| `eggpool stats recompute-costs [--dry-run\|--apply] [--limit N]` | Recompute historical `cost_microdollars` from current price snapshots. Default `--dry-run`. |
|
|
187
|
+
| `eggpool deploy systemd` | Print systemd unit; `--install` writes it (personal by default; `--production` for the dedicated-system layout; `--as-root` for a root-owned personal unit) |
|
|
188
|
+
| `eggpool deploy cron` | Print / install / uninstall the watchdog crontab (`@reboot` + `*/N * * * *` `eggpool ensure-running`). `--interval N` (1-59, default 5) |
|
|
189
|
+
| `eggpool deploy backup-cron` | Print / install / uninstall the daily backup cron (personal user cron or production `/etc/cron.d/`) |
|
|
190
|
+
| `eggpool deploy logrotate` | Print / install / logrotate config (validated via `logrotate -d`) |
|
|
191
|
+
| `eggpool deploy all` | Print / install systemd + logrotate + watchdog cron (backup-cron is separate) |
|
|
192
|
+
| `eggpool backup` | Create a timestamped `.zip` backup of config, `.env`, and database |
|
|
193
|
+
| `eggpool recover [path]` | Restore from a backup archive (interactive menu if no path) |
|
|
194
|
+
| `eggpool uninstall` | Remove binary, config, database, and shell PATH entries; `--deploy-artifacts` also removes the systemd unit, logrotate config, watchdog + backup cron blocks, and backup script |
|
|
195
|
+
|
|
196
|
+
All commands accept `--config /path/to/config.toml` (defaults to `config.toml`; resolution: `--config` > `$EGGPOOL_CONFIG` > `~/.config/eggpool/config.toml` > `./config.toml`).
|
|
197
|
+
Running `eggpool` with no arguments prints the help message.
|
|
198
|
+
|
|
199
|
+
## Git Workflow
|
|
200
|
+
|
|
201
|
+
- Branch: `main`
|
|
202
|
+
- Commit messages: concise, imperative mood
|
|
203
|
+
- Never commit secrets, API keys, or `.env` files
|
|
@@ -7,6 +7,105 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.0] - 2026-06-25
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Daemon mode**: `eggpool serve --daemon` spawns a detached supervisor
|
|
15
|
+
and returns the shell promptly. The child runs the normal foreground
|
|
16
|
+
`serve` command (Granian supervisor + worker); `--daemon` is never
|
|
17
|
+
forwarded. Flags: `--log-file PATH`, `--quiet`, `--as-root`. Default
|
|
18
|
+
log destination is `~/.local/state/eggpool/eggpool.log`.
|
|
19
|
+
- **Fast-path CLI**: `eggpool ensure-running` and `eggpool croncheck`
|
|
20
|
+
are dispatched without importing Click via `eggpool.fastcli`. Both
|
|
21
|
+
modules are stdlib-only, keeping cron watchdog ticks cheap on
|
|
22
|
+
Raspberry Pi-class hardware.
|
|
23
|
+
- **`eggpool runtime-status`**: compact terminal health summary from
|
|
24
|
+
the running server (process topology, memory, background tasks,
|
|
25
|
+
database health, in-flight requests). Supports `--json` for
|
|
26
|
+
scripting.
|
|
27
|
+
- **Grouped timeseries dashboard**: stacked-bar chart on `/timeseries`
|
|
28
|
+
with groupable dimensions (`provider_model`, `provider`, `model`,
|
|
29
|
+
`account`), top-N + Other folding, per-bucket detail table, and
|
|
30
|
+
interactive controls for period, bucket, group_by, metric, and
|
|
31
|
+
limit. Backed by `/api/timeseries/grouped`.
|
|
32
|
+
- **Metrics dashboard**: reliability page (attempt success/retry
|
|
33
|
+
breakdown, `retry_category` distribution, pending health, operational
|
|
34
|
+
events), routing page (per-`(model, provider)` decision aggregates,
|
|
35
|
+
account selection counts, exclusion taxonomy), traces page
|
|
36
|
+
(auth-gated recent request metadata), latency phase decomposition
|
|
37
|
+
(`upstream_connect_ms`, `upstream_read_ms`, `coordinator_overhead_ms`).
|
|
38
|
+
- **CSS tooltip system**: pure-CSS `[data-tooltip]` bubbles on heatmap
|
|
39
|
+
cells, column headers, topbar controls, and status badges. No
|
|
40
|
+
JavaScript; survives overview auto-refresh `innerHTML` swap.
|
|
41
|
+
- **Upstream-authoritative suppression**: local quota estimates are
|
|
42
|
+
advisory by default (`local_quota_mode = "score_only"`). Above-capacity
|
|
43
|
+
accounts stay eligible; only upstream-observed failures (429/402/5xx/auth)
|
|
44
|
+
and explicit operator disablement suppress routing. Opt in to legacy
|
|
45
|
+
behavior via `local_quota_mode = "hard_cap"`.
|
|
46
|
+
- **Runtime/ops metrics**: `/api/stats/runtime` endpoint exposes
|
|
47
|
+
process topology, memory, background task state, database health,
|
|
48
|
+
and in-flight request counts. `/runtime` dashboard page renders
|
|
49
|
+
these metrics.
|
|
50
|
+
- **Attempt analytics**: per-attempt aggregates including latency
|
|
51
|
+
percentiles, byte totals, retry rate, and `retry_category`
|
|
52
|
+
distribution. Every `request_attempts` row carries
|
|
53
|
+
`provider_id/model_id/protocol/retry_category/release_reason/bytes_received/latency_ms/streamed/is_retry_outcome`.
|
|
54
|
+
- **Routing analytics**: per-`(model, provider)` decision aggregates,
|
|
55
|
+
account-level selection counts, and per-`(account, reason)` exclusion
|
|
56
|
+
counts. Every routing decision persisted to `routing_decisions` in
|
|
57
|
+
the same transaction as the `request_attempts` INSERT.
|
|
58
|
+
- **Operational health**: `crash_recovery`, `stale_request_finalizer`,
|
|
59
|
+
and `reservation_reconcile` safety-net events recorded as
|
|
60
|
+
`operational_events` rows.
|
|
61
|
+
- **Pricing provenance**: `source_detail` and `source_confidence`
|
|
62
|
+
columns on `model_price_snapshots` for dashboard attribution.
|
|
63
|
+
Migration `0031_price_snapshot_provenance.sql`.
|
|
64
|
+
- **Pricing alias registry**: maps upstream model IDs to external
|
|
65
|
+
catalog IDs with `exact`/`curated_alias`/`ambiguous_skip` confidence.
|
|
66
|
+
Migration `0030_model_pricing_aliases.sql`. Seeded idempotently at
|
|
67
|
+
startup.
|
|
68
|
+
- **Install/deploy simplification**: `eggpool deploy systemd --install`
|
|
69
|
+
personal mode auto-detects user, binary, and config paths.
|
|
70
|
+
`eggpool deploy backup-cron` and `eggpool deploy all` for complete
|
|
71
|
+
lifecycle management.
|
|
72
|
+
- `eggpool stats recompute-costs [--dry-run|--apply] [--limit N]`
|
|
73
|
+
operator escape hatch for fixing inflated cost totals after resolver
|
|
74
|
+
upgrades.
|
|
75
|
+
- `eggpool init-config` writes bundled `config.example.toml` to
|
|
76
|
+
current directory or target path.
|
|
77
|
+
|
|
78
|
+
### Fixed
|
|
79
|
+
|
|
80
|
+
- 503 saturation after several minutes of streaming load. Streaming
|
|
81
|
+
request finalization is now wrapped in
|
|
82
|
+
`asyncio.shield(asyncio.wait_for(..., timeout=10))` so ASGI task
|
|
83
|
+
cancellation cannot kill the finalizer while it holds the SQLite
|
|
84
|
+
connection lock. A periodic `stale_request_finalizer` background
|
|
85
|
+
task force-finalizes any request that has been `pending` longer than
|
|
86
|
+
`upstream.read_timeout_s` and reconciles the in-memory active-count
|
|
87
|
+
and quota-reservation caches. Startup `_crash_recovery` no longer
|
|
88
|
+
time-gates its sweep — a process restart is treated as a definitive
|
|
89
|
+
boundary, so every leaked pending request and every active
|
|
90
|
+
reservation from the previous process is recovered on boot.
|
|
91
|
+
- MiMo-style cost inflation via provider-aware pricing resolution.
|
|
92
|
+
The resolver now correctly handles cached-token-heavy models where
|
|
93
|
+
upstream metadata reports different pricing than the external catalog.
|
|
94
|
+
- Stale `format_tokens` assertion after unit-scaling rewrite.
|
|
95
|
+
- MiniMax and GeneralCompute provider contract alignment: auth headers,
|
|
96
|
+
URL composition, and static model seeding now match the documented
|
|
97
|
+
contracts.
|
|
98
|
+
|
|
99
|
+
### Changed
|
|
100
|
+
|
|
101
|
+
- `eggpool serve` runs as a single supervisor process invoking Granian
|
|
102
|
+
with `workers=1`. The supervisor owns the PID file; the FastAPI
|
|
103
|
+
lifespan no longer touches it.
|
|
104
|
+
- `eggpool restart` delegates to `runtime.restart_server` instead of
|
|
105
|
+
inlining subprocess logic.
|
|
106
|
+
- AGENTS.md trimmed to point to skills for details; CLI commands
|
|
107
|
+
table expanded to cover all 35+ commands.
|
|
108
|
+
|
|
10
109
|
## [0.2.2] - 2026-06-25
|
|
11
110
|
|
|
12
111
|
### Fixed
|