eggpool 0.3.0__tar.gz → 0.3.2__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 → eggpool-0.3.2}/AGENTS.md +41 -3
- {eggpool-0.3.0 → eggpool-0.3.2}/CHANGELOG.md +7 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/PKG-INFO +92 -15
- {eggpool-0.3.0 → eggpool-0.3.2}/README.md +90 -13
- {eggpool-0.3.0 → eggpool-0.3.2}/architecture/README.md +13 -5
- {eggpool-0.3.0 → eggpool-0.3.2}/config.example.toml +90 -19
- {eggpool-0.3.0 → eggpool-0.3.2}/deploy/eggpool.service +1 -1
- {eggpool-0.3.0 → eggpool-0.3.2}/docs/backup-restore.md +73 -1
- {eggpool-0.3.0 → eggpool-0.3.2}/docs/deployment.md +100 -9
- eggpool-0.3.2/docs/network-diagnostics.md +175 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/docs/providers.md +26 -21
- {eggpool-0.3.0 → eggpool-0.3.2}/pyproject.toml +2 -2
- {eggpool-0.3.0 → eggpool-0.3.2}/scripts/check_database.py +1 -1
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/_share/config.example.toml +22 -19
- eggpool-0.3.2/src/eggpool/api/network.py +91 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/api/stats.py +1 -0
- eggpool-0.3.2/src/eggpool/api/update.py +66 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/app.py +178 -16
- eggpool-0.3.2/src/eggpool/background/backup.py +130 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/catalog/catalog_resolvers.py +33 -13
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/catalog/normalizer.py +28 -2
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/catalog/pricing_aliases.py +29 -28
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/catalog/service.py +23 -2
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/cli_full.py +185 -33
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/escape.py +2 -2
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/render.py +1400 -818
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/routes.py +152 -128
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/static/dashboard.css +313 -5
- eggpool-0.3.2/src/eggpool/dashboard/static/dashboard.js +867 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/connection.py +32 -0
- eggpool-0.3.2/src/eggpool/db/rollup_repository.py +335 -0
- eggpool-0.3.2/src/eggpool/db/schema/0032_usage_rollups.sql +60 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/checksums.json +2 -1
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/deploy/__init__.py +1 -1
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/lifecycle/backup.py +189 -21
- eggpool-0.3.2/src/eggpool/metrics/__init__.py +1 -0
- eggpool-0.3.2/src/eggpool/metrics/buffer.py +342 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/models/config.py +101 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/providers/_templates.toml +16 -45
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/providers/client_pool.py +39 -5
- eggpool-0.3.2/src/eggpool/providers/dns_cache.py +425 -0
- eggpool-0.3.2/src/eggpool/providers/outbound.py +312 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/request/coordinator.py +14 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/request/finalizer.py +36 -0
- eggpool-0.3.2/src/eggpool/runtime_dispatch.py +83 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/runtime_metrics.py +176 -10
- eggpool-0.3.2/src/eggpool/stats/grouped_timeseries.py +325 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/stats/queries.py +19 -343
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/stats/service.py +324 -15
- eggpool-0.3.2/src/eggpool/update_checker.py +390 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/uv.lock +79 -3
- eggpool-0.3.0/src/eggpool/dashboard/static/dashboard.js +0 -476
- {eggpool-0.3.0 → eggpool-0.3.2}/.env.example +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/.github/workflows/ci.yml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/.github/workflows/release.yml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/.gitignore +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/LICENSE +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/config-examples/claude-code.env +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/config-examples/opencode.jsonc +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/deploy/eggpool-logrotate.conf +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/deploy/env.example +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/docs/filesystem-layout.md +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/docs/firewall.md +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/docs/model-limits.md +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/docs/proxy.md +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/docs/raspberry-pi.md +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/scripts/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/scripts/install.sh +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/scripts/install_prompt.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/scripts/smoke_test.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/scripts/verify_upstream_auth.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/__main__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/_share/.env.example +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/accounts/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/accounts/registry.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/accounts/state.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/api/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/api/backoff.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/api/chat_completions.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/api/errors.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/api/messages.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/api/models.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/api/proxy_request.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/api/runtime.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/auth.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/background/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/background/cleanup.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/catalog/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/catalog/cache.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/catalog/fetcher.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/catalog/limits.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/catalog/pricing.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/catalog/pricing_resolver.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/catalog/protocols.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/cli.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/config.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/constants.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/cost_recompute.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/_resources.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/static/chart.umd.min.js +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/static/favicon.svg +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/theme.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Booberry.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Catppuccin Latte.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Catppuccin Macchiato.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Catppuccin Mocha.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Cyber Red.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Cyberpunk.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Dark Green.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Discord (80_ Saturation).toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Discord.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Dracula.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Ferra Light.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Flexor Dark.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Gruvbox.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Halcyon Dark.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/IntelliJ Light.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Kanagawa.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Macaw Dark.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Macaw Light.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Matrix.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Noctis Lilac.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Nord.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Nostromo Terminal.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/One Dark.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Oxocarbon.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Rose Pine Dawn.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Rose Pine Moon.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Rose Pine.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Solarized Dark.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Sonokai.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Tokyo Night Storm.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/VESPER.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/Zenburn.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/acton.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/bam.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/base16-atelier-forest-light.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/berlin.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/black but with important highlights.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/broc.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/cork.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/ferra.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/forest.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/lisbon.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/midnight.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/oslo.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/plum.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/portland.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/sunset.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/tofino.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/vanimo.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/dashboard/themes/vik.toml +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/migrations.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/repositories.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0001_initial.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0002_indexes.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0003_request_attempts.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0004_integration_hardening.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0005_price_microdollars.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0006_correct_price_microdollars.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0007_price_cache_rates.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0008_proxy_request_identity.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0009_model_protocol_source.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0010_health_probe.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0011_model_resolution_status.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0012_drop_reservations_estimated_microdollars.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0013_request_attempts_account_id_index.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0014_bandwidth_tracking.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0015_multi_provider.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0016_requests_provider_id.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0017_price_snapshots_provider_id.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0018_provider_pings.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0019_client_ip.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0020_performance_indexes.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0021_provider_model_metadata.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0022_dashboard_indexes.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0023_deprecated_model_placeholder.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0024_account_backoffs.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0025_stale_request_index.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0026_attempt_observability.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0027_routing_decisions.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0028_operational_events.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0029_latency_phases.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0030_model_pricing_aliases.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/db/schema/0031_price_snapshot_provenance.sql +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/deploy_user.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/errors.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/fastcli.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/health/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/health/backoff.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/health/circuit_breaker.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/health/health_manager.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/integrations/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/integrations/opencode.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/lifecycle/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/lifecycle/uninstall.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/logging.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/models/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/models/api.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/models/database.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/models/domain.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/onboard.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/providers/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/providers/auth.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/providers/connect.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/providers/contract.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/providers/pproxy_transport.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/proxy/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/proxy/client.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/proxy/sse_observer.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/proxy/usage.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/py.typed +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/quota/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/quota/audit.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/quota/estimation.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/quota/reservation.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/quota/scorer.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/request/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/request/attempt_finalizer.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/request/body.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/request/limits.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/retry/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/retry/classification.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/routing/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/routing/eligibility.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/routing/provider.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/routing/router.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/runtime.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/runtime_paths.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/security/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/security/redaction.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/stats/__init__.py +0 -0
- {eggpool-0.3.0 → eggpool-0.3.2}/src/eggpool/toml_edit.py +0 -0
|
@@ -52,8 +52,12 @@ All four must pass with zero errors.
|
|
|
52
52
|
|
|
53
53
|
## Multi-Provider Architecture
|
|
54
54
|
|
|
55
|
+
> See `architecture/README.md` and the `architecture` skill for the full design, worked examples, and MiniMax template details.
|
|
56
|
+
|
|
55
57
|
- 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
|
|
58
|
+
- `ProviderClientPool` manages per-provider `httpx.AsyncClient` with independent connection pools for upstream LLM forwarding and catalog model-list fetches
|
|
59
|
+
- `OutboundClientManager` owns a shared `httpx.AsyncClient` for non-provider network paths (update checks, external catalog fetches). Initialized once at startup; `build_count` should stabilize at 1. Accepts `[network]` config for transport tuning and `[network.dns_cache]` for in-memory DNS caching (enabled by default, TTL 300s, max 50 entries). The DNS cache wraps the default httpcore transport so resolved entries are reused across requests. Exposes `snapshot()` with `build_count`, `request_count`, `error_count` for runtime diagnostics. `inject_client()` is the test escape hatch
|
|
60
|
+
- Hot-path provider requests must **never** construct fresh HTTP clients. Background and CLI paths should use the shared outbound client. `warn_adhoc_clientConstruction()` emits a runtime warning after startup when fresh clients are built outside managed paths
|
|
57
61
|
- Flat `[[accounts]]` configs auto-normalize to a default `opencode-go` provider
|
|
58
62
|
- `parse_model_provider()` and `format_model_provider()` in `routing/provider.py`
|
|
59
63
|
|
|
@@ -85,7 +89,7 @@ Use the hierarchy in `errors.py`. Chain exceptions with `raise ... from err` or
|
|
|
85
89
|
## Gotchas
|
|
86
90
|
|
|
87
91
|
- Configuration changes require a service restart; live reload is intentionally not supported
|
|
88
|
-
- No
|
|
92
|
+
- No pre-commit hooks are configured in this repo; CI runs ruff, pyright, and pytest via GitHub Actions
|
|
89
93
|
- `Database.vacuum()` is the only sanctioned path for `VACUUM` in production code
|
|
90
94
|
- Every DML write must run inside `async with db.transaction():`
|
|
91
95
|
- SQLite transactions are serialized across concurrent tasks via a single connection lock + ContextVar
|
|
@@ -98,9 +102,16 @@ Use the hierarchy in `errors.py`. Chain exceptions with `raise ... from err` or
|
|
|
98
102
|
- **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
103
|
- **`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
104
|
- **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()`.
|
|
105
|
+
- **Migration 0032 (`usage_rollups`)**: introduces the rollup table for buffered analytics. Counter fields are designed for additive upserts (`INSERT ... ON CONFLICT DO UPDATE SET col = col + excluded.col`). Latency min/max use `CASE/WHEN` to converge monotonically within each bucket. The `UsageRollupRepository` provides `upsert_many()`, `query_timeseries()`, `query_summary()`, and `cleanup_old_rollups()`.
|
|
106
|
+
- **Low-wear metrics buffering**: `MetricsWriteCoalescer` buffers lossy analytics events in memory and periodically flushes to `usage_rollups`. Correctness-critical writes (request state, reservations, routing) remain immediate. `write_mode = "balanced"` (default) uses 30s flush intervals; `write_mode = "low_wear"` uses 120s with coarser buckets. Buffered data may lose at most `flush_interval_s` seconds after abrupt power loss. The coalescer is wired into `RequestFinalizer.finalize()` and emits one `UsageMetricEvent` per terminal transition. Runtime diagnostics expose buffer health via `/api/stats/runtime`.
|
|
107
|
+
- **Dispatch overhead and OS load average on `/runtime`**: `DispatchOverheadRecorder` (`src/eggpool/runtime_dispatch.py`) records `time.perf_counter_ns() - context.started_monotonic_ns` immediately before `client.send(...)` in both `_execute_non_streaming` and `_execute_streaming`, on every upstream attempt (so retries contribute). The recorder is bounded (`deque(maxlen=100)`, thread-safe), stores only integer nanoseconds (no body, model ID, account name, auth header, or client IP), and never writes to SQLite. `RuntimeMetricsService.snapshot()` exposes `dispatch_overhead` (avg/min/max/p50/p95) and `load` (`os.getloadavg` 1m/5m/15m + normalized per-core; `available: false` on platforms without it). The Runtime dashboard drops the configured-thread and process-count cards in favor of `Active threads`, `Load average`, and `Dispatch overhead`; process-count anomalies still surface as a warning-only panel
|
|
108
|
+
- **Automatic backups**: in-process daily backups run by default under the `automatic_backup` supervised task (`src/eggpool/background/backup.py`). Uses stdlib `sqlite3.Connection.backup()` for consistent snapshots, atomic archive publication (write-to-temp + rename), and count-based retention (default 14). Controlled by `[backup]` config section. The `eggpool deploy backup-cron` path remains available for operators who prefer external scheduling.
|
|
109
|
+
- **DNS cache**: `OutboundClientManager` and `ProviderClientPool` both integrate a `DnsNetworkBackend` that caches resolved DNS entries in memory. The cache reduces connection latency for repeated requests to the same upstream hosts. Controlled by `[network.dns_cache]` config. When a proxy is configured for an account, that account's client uses the proxy transport instead of the cached backend.
|
|
101
110
|
|
|
102
111
|
## Observability
|
|
103
112
|
|
|
113
|
+
> Full API surface, attempt analytics, routing analytics, and latency phase details are in the `architecture` skill.
|
|
114
|
+
|
|
104
115
|
- **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
116
|
- **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
117
|
- **Latency phases**: decomposes each request into `upstream_connect_ms`, `upstream_read_ms`, and `coordinator_overhead_ms`
|
|
@@ -109,16 +120,43 @@ Use the hierarchy in `errors.py`. Chain exceptions with `raise ... from err` or
|
|
|
109
120
|
- **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
121
|
- **Recent request metadata**: bounded list of recent request rows with metadata only (no body, no auth headers, no error_detail). Auth-gated
|
|
111
122
|
- **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`
|
|
123
|
+
- **Metrics buffer health**: `metrics.write_mode`, `metrics.flush_interval_s`, `metrics.buffered_keys`, `metrics.buffered_events`, `metrics.total_events_received`, `metrics.total_events_flushed`, `metrics.total_events_dropped`, `metrics.last_flush_ts`, `metrics.last_flush_rows`, `metrics.last_flush_duration_ms`, `metrics.last_flush_error`. Exposed via `/api/stats/runtime` and `eggpool runtime-status`
|
|
124
|
+
- **Network diagnostics** (`/api/network/diagnostics`): sanitized outbound client lifecycle (build count, request count, error count, per-scope builds, per-host requests/errors) and DNS cache behavior (hits, misses, negative hits, stale hits, evictions, per-host breakdown, resolution errors, per-entry metadata with state/TTL/staleness). Always auth-gated. Also displayed on the `/runtime` dashboard page and in `eggpool runtime-status` output. No API keys, auth headers, request bodies, or full URLs are exposed
|
|
112
125
|
- Full API surface is documented in the `architecture` skill
|
|
113
126
|
|
|
114
127
|
## Dashboard
|
|
115
128
|
|
|
129
|
+
> Full page list, chart lifecycle, grouped timeseries, tooltip system, and responsive/mobile details are in the `architecture` skill.
|
|
130
|
+
|
|
116
131
|
- Server-rendered HTML pages in `src/eggpool/dashboard/render.py`
|
|
117
132
|
- Overview page auto-refreshes in place (every `[dashboard].refresh_interval_s`); all other pages are static
|
|
118
133
|
- Charts use bundled Chart.js v4 at `/static/chart.js` with `Cache-Control: public, max-age=86400`
|
|
119
134
|
- New pages opt into Chart.js via `include_chart_js=True` in `_render_layout`
|
|
120
135
|
- Frontend helpers in `src/eggpool/dashboard/static/dashboard.js` under `window.EggPoolDashboard`
|
|
121
136
|
- Full page list and chart lifecycle details are in the `architecture` skill
|
|
137
|
+
- **`header.topbar` is `position: sticky; top: 0; z-index: 5`** with a subtle backdrop blur so the page nav stays visible while scrolling on desktop. Mobile layout is unchanged — the topnav disclosure still wraps cleanly under 480px. Use `header.topbar` selectors when overriding; do not collapse the existing z-index above the value used by `body::before` (2) or the egg background (0)
|
|
138
|
+
- **Footer update indicator**: `_render_update_indicator(update_info)` renders an empty string when `update_info is None` or `update_info.update_available is False`. The indicator is appended to the footer after `<span id="dashboard-updated">ready</span>` and contains a `data-update-command` markup hook consumed by `dashboard.js` `initUpdateCommandCopy()`. The hook uses Clipboard API with `document.execCommand("copy")` fallback for older browsers and surfaces a transient "copied!" indicator on Enter/Space/click
|
|
139
|
+
|
|
140
|
+
### Update Checker
|
|
141
|
+
|
|
142
|
+
- `src/eggpool/update_checker.py` is the single source of truth for "is there a newer eggpool release available?"
|
|
143
|
+
- `UpdateChecker` (frozen `UpdateInfo` dataclass) holds the latest snapshot; `snapshot()` returns `dataclasses.replace(self._info)` so callers cannot mutate the cached state. Writes are serialized through an `asyncio.Lock` so the periodic background task and synchronous `snapshot()` calls from request handlers do not tear
|
|
144
|
+
- `async_check_for_update()` is the shared helper used by both the dashboard background task and the `eggpool update` CLI — they MUST go through it instead of inlining their own PyPI lookup so the two paths cannot drift
|
|
145
|
+
- Default check interval is 24h (`_DEFAULT_CHECK_INTERVAL_S = 24 * 60 * 60`), timeout 15s (`_CHECK_TIMEOUT_S = 15.0`)
|
|
146
|
+
- The background task is registered in `src/eggpool/app.py` via `supervisor.register("update_checker", update_checker.run_periodic)` and uses the default supervisor `max_restarts` so transient PyPI failures do not give up
|
|
147
|
+
- On PyPI failure, the checker preserves the previous `latest_version` so the indicator still surfaces a known-newer release during momentary outages
|
|
148
|
+
- `/api/stats/update` (`src/eggpool/api/update.py`) returns the JSON snapshot — always auth-gated regardless of `dashboard.public`
|
|
149
|
+
- Add new dashboard footer UI by extending `_UPDATE_INDICATOR_TEMPLATE` in `src/eggpool/dashboard/render.py:259`; ensure `_render_update_indicator` continues to return `""` whenever an update is not available so the footer does not show a stale or "no update" pill
|
|
150
|
+
|
|
151
|
+
### Responsive / mobile
|
|
152
|
+
|
|
153
|
+
- Dashboard targets a **320px minimum** viewport (iPhone SE / small Android). All 12 pages are mobile-equally
|
|
154
|
+
- Topnav wraps page links in a `<details class="topnav-hamburger">` disclosure; below 480px the `<summary>` becomes a Menu chip and the 12 links collapse into it. Theme selector and refresh button stay **outside** the disclosure so they remain reachable on every viewport
|
|
155
|
+
- Tables use `data-priority="N"` on every `<th>` and matching `<td>` to drive responsive column hiding: `P1` always shown, `P2` hidden below 480px, `P3` hidden below 760px. Renderers MUST use the `_th(label, *, priority=1)` and `_td_priority(content, priority, *, class_=None)` helpers in `src/eggpool/dashboard/render.py:460` — never emit a bare `<th>` for a tabular column
|
|
156
|
+
- Chart.js canvases MUST sit inside a `<div class="chart-wrap" style="height: …">` (not an inline-style `<div style="position: relative; height: …">`). `.chart-wrap` sets `position: relative; width: 100%` so the canvas respects its parent panel's width on every viewport
|
|
157
|
+
- `html { overflow-x: hidden }` is a deliberate safety net in `dashboard.css` to prevent chart-canvas resize races from causing horizontal scroll on the body. It hides real overflow bugs, so any new layout code should still design for `clientWidth == scrollWidth` even though it's masked
|
|
158
|
+
- New tables: pick priority mappings that put operator-glance columns in P1, diagnostic core in P2, and deep-tail columns in P3. Update `tests/unit/test_dashboard.py` `TestResponsiveColumns` with a snapshot test
|
|
159
|
+
- Adding a new topnav link requires updating both `_render_nav` and the breakpoints in `dashboard.css` (`@media (max-width: 480px)` and `topnav-hamburger > .topnav-links { … }`)
|
|
122
160
|
|
|
123
161
|
## Fast-Path CLI
|
|
124
162
|
|
|
@@ -186,7 +224,7 @@ Use the hierarchy in `errors.py`. Chain exceptions with `raise ... from err` or
|
|
|
186
224
|
| `eggpool stats recompute-costs [--dry-run\|--apply] [--limit N]` | Recompute historical `cost_microdollars` from current price snapshots. Default `--dry-run`. |
|
|
187
225
|
| `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
226
|
| `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/`) |
|
|
227
|
+
| `eggpool deploy backup-cron` | Print / install / uninstall the daily backup cron (personal user cron or production `/etc/cron.d/`). Optional — in-process automatic backup runs by default |
|
|
190
228
|
| `eggpool deploy logrotate` | Print / install / logrotate config (validated via `logrotate -d`) |
|
|
191
229
|
| `eggpool deploy all` | Print / install systemd + logrotate + watchdog cron (backup-cron is separate) |
|
|
192
230
|
| `eggpool backup` | Create a timestamped `.zip` backup of config, `.env`, and database |
|
|
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Sticky dashboard topbar**: `header.topbar` is `position: sticky; top: 0; z-index: 5` with a subtle backdrop blur, so the page navigation stays visible while scrolling on desktop. Mobile layout is unchanged (the topnav disclosure still wraps cleanly under 480px).
|
|
13
|
+
- **Footer update indicator**: periodic PyPI check (default 24h interval, 15s timeout) drives a footer pill that appears only when a newer `eggpool` release is available. The pill shows the current and latest versions side-by-side and the one-liner command (`eggpool update`) in an inline-code block. Clicking the command copies it to the clipboard via the bundled `dashboard.js` (Clipboard API with `execCommand("copy")` fallback); a transient "copied!" indicator confirms success. The new `src/eggpool/update_checker.py` module is the single source of truth for PyPI lookups — both the dashboard background task and the `eggpool update` CLI share `async_check_for_update()` so the two paths cannot drift.
|
|
14
|
+
- **`/api/stats/update` endpoint**: auth-gated JSON snapshot of the latest `UpdateChecker` state (`current_version`, `latest_version`, `update_available`, `last_checked_at`, `last_error`). Returns an empty payload if the checker has not yet produced a snapshot. Always auth-gated regardless of `dashboard.public`.
|
|
15
|
+
- **Runtime dispatch overhead and load metrics**: `DispatchOverheadRecorder` (`src/eggpool/runtime_dispatch.py`) records `time.perf_counter_ns() - context.started_monotonic_ns` immediately before `client.send(...)` in both `_execute_non_streaming` and `_execute_streaming`, on every upstream attempt (retries included). Bounded `deque(maxlen=100)`, thread-safe, integer-nanosecond storage — no body, model ID, account name, auth header, or client IP ever enters the buffer. `RuntimeMetricsService.snapshot()` gains two top-level sections: `dispatch_overhead` (avg/min/max/p50/p95 over the last 100 attempts) and `load` (`os.getloadavg` 1m/5m/15m + normalized per-core; `available: false` on platforms without it). The Runtime dashboard drops the configured-thread and process-count cards in favor of `Active threads`, `Load average`, and `Dispatch overhead`; process-count anomalies surface as a warning-only panel. `eggpool runtime-status` and `docs/deployment.md` document the new metrics.
|
|
16
|
+
|
|
10
17
|
## [0.3.0] - 2026-06-25
|
|
11
18
|
|
|
12
19
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: eggpool
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: A lightweight proxy that aggregates multiple LLM provider accounts behind one OpenAI-compatible endpoint
|
|
5
5
|
Project-URL: Homepage, https://github.com/eggstack/eggpool
|
|
6
6
|
Project-URL: Repository, https://github.com/eggstack/eggpool
|
|
@@ -27,7 +27,7 @@ Requires-Python: >=3.11
|
|
|
27
27
|
Requires-Dist: aiosqlite
|
|
28
28
|
Requires-Dist: click
|
|
29
29
|
Requires-Dist: fastapi
|
|
30
|
-
Requires-Dist: granian
|
|
30
|
+
Requires-Dist: granian[pname]
|
|
31
31
|
Requires-Dist: httpx
|
|
32
32
|
Requires-Dist: pproxy>=2.7.9
|
|
33
33
|
Requires-Dist: pydantic>=2.0
|
|
@@ -145,12 +145,25 @@ eggpool deploy cron --install
|
|
|
145
145
|
|
|
146
146
|
This writes a `@reboot` + `*/5 * * * *` `eggpool ensure-running` block to the invoking user's crontab (or `SUDO_USER`'s crontab under sudo). Use `--interval N` to change the poll cadence. The block is bracketed by `# BEGIN EggPool watchdog` / `# END EggPool watchdog` markers so uninstall only strips the eggpool-owned lines. See [docs/deployment.md](docs/deployment.md) for the full design.
|
|
147
147
|
|
|
148
|
-
### Backup and
|
|
148
|
+
### Backup and restore
|
|
149
149
|
|
|
150
|
-
EggPool
|
|
150
|
+
EggPool creates **automatic daily backups** by default. The in-process `automatic_backup` supervised task produces restore-compatible `.zip` archives every 24 hours (after an initial 5-minute startup delay) and retains the last 14. Archives are stored under `$XDG_BACKUP_HOME/eggpool` or `~/backups/eggpool`.
|
|
151
|
+
|
|
152
|
+
```toml
|
|
153
|
+
# Optional — automatic backups are enabled by default
|
|
154
|
+
[backup]
|
|
155
|
+
enabled = true
|
|
156
|
+
interval_s = 86400
|
|
157
|
+
retain_count = 14
|
|
158
|
+
startup_delay_s = 300
|
|
159
|
+
# directory = "/path/to/backups"
|
|
160
|
+
include_env = true
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Manual backup and restore are also available:
|
|
151
164
|
|
|
152
165
|
```bash
|
|
153
|
-
#
|
|
166
|
+
# Manual backup config + .env + database
|
|
154
167
|
eggpool backup
|
|
155
168
|
|
|
156
169
|
# Restore from a specific archive (or omit the path for an interactive menu)
|
|
@@ -163,7 +176,7 @@ eggpool uninstall --yes
|
|
|
163
176
|
eggpool uninstall --yes --deploy-artifacts
|
|
164
177
|
```
|
|
165
178
|
|
|
166
|
-
|
|
179
|
+
`eggpool deploy backup-cron` is optional and mainly for operators who prefer external scheduling or want backups even when the server process is not running. See [docs/backup-restore.md](docs/backup-restore.md) for the full backup/restore workflow.
|
|
167
180
|
|
|
168
181
|
## CLI Commands
|
|
169
182
|
|
|
@@ -200,7 +213,7 @@ The uninstall command removes the binary, active config, `.env`, database, and `
|
|
|
200
213
|
| `eggpool init-config` | Write bundled config.example.toml to current directory or TARGET |
|
|
201
214
|
| `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) |
|
|
202
215
|
| `eggpool deploy cron` | Print / install / uninstall the **watchdog** crontab (`@reboot` + `*/N * * * *` `ensure-running`). `--interval N` (1-59, default 5) |
|
|
203
|
-
| `eggpool deploy backup-cron` | Print / install / uninstall the daily backup cron
|
|
216
|
+
| `eggpool deploy backup-cron` | Print / install / uninstall the daily backup cron. Optional — in-process automatic backup runs by default |
|
|
204
217
|
| `eggpool deploy logrotate` | Print / install the logrotate config (validated via `logrotate -d`) |
|
|
205
218
|
| `eggpool deploy all` | Print / install systemd + logrotate + watchdog cron (backup-cron is separate) |
|
|
206
219
|
| `eggpool backup` | Create a timestamped `.zip` backup (default `~/backups/eggpool/`) |
|
|
@@ -291,7 +304,7 @@ The dashboard includes:
|
|
|
291
304
|
- Overview with request counts, error rates, costs, token usage, and a System Health row surfacing pending-request and reservation leaks
|
|
292
305
|
- Reliability page (`/reliability`) with attempt success/retry breakdown, `retry_category` distribution, pending health, and operational events
|
|
293
306
|
- Routing page (`/routing`) with per-`(model, provider)` decision aggregates, account selection counts, and exclusion taxonomy (suppressive vs advisory)
|
|
294
|
-
- Runtime page (`/runtime`) with
|
|
307
|
+
- Runtime page (`/runtime`) with RSS memory, open file descriptors, active thread count, OS load average, dispatch overhead (last 100 upstream attempts), background task status, database health, and in-flight request counts. Process count surfaces only when anomalous
|
|
295
308
|
- Traces page (`/traces`) with auth-gated recent request metadata (no error_detail, no client_ip)
|
|
296
309
|
- Account and model breakdowns with filtering, exactness columns, cache/reasoning ratios, and cost-per-1k-tokens
|
|
297
310
|
- Latency metrics including time-to-first-token (TTFT) and connect/read/coordinator-overhead phase breakdown
|
|
@@ -317,8 +330,8 @@ appropriate cache headers.
|
|
|
317
330
|
JSON stats endpoints are available under `/api/stats/*`, including summary,
|
|
318
331
|
accounts, models, timeseries, errors, latency, pings, bandwidth, attempts,
|
|
319
332
|
retries, routing, routing-selections, routing-exclusions, operational,
|
|
320
|
-
pending-health, runtime, recent-requests, recent/{request_id},
|
|
321
|
-
`/api/events`. The recent-requests, recent/{request_id}, pending-health,
|
|
333
|
+
pending-health, runtime, recent-requests, recent/{request_id},
|
|
334
|
+
`/api/stats/update`, and `/api/events`. The recent-requests, recent/{request_id}, pending-health,
|
|
322
335
|
and runtime endpoints are always auth-gated (even when the dashboard is
|
|
323
336
|
public) because they expose per-request metadata (model, prompt volume,
|
|
324
337
|
error class), operational state (pending reservations, reserved cost),
|
|
@@ -373,6 +386,18 @@ dashboard's public/auth setting.
|
|
|
373
386
|
`source_confidence` (exact external ID vs. curated alias vs. unknown).
|
|
374
387
|
Used by the dashboard to render the cost-exactness badge and the
|
|
375
388
|
high-spend estimated warning.
|
|
389
|
+
- **Update checker** (`/api/stats/update`): the server checks PyPI at startup
|
|
390
|
+
and approximately every 24 hours for newer eggpool releases. When an update
|
|
391
|
+
is found, the dashboard footer shows a non-intrusive indicator with the
|
|
392
|
+
current and latest versions and a copyable `eggpool update` command. The
|
|
393
|
+
JSON snapshot is also available at `GET /api/stats/update`.
|
|
394
|
+
- **Network diagnostics** (`/api/network/diagnostics`): sanitized outbound
|
|
395
|
+
client lifecycle (build count, request count, error count, per-scope builds,
|
|
396
|
+
per-host requests/errors) and DNS cache behavior (hits, misses, negative
|
|
397
|
+
hits, stale hits, evictions, per-host breakdown, resolution errors,
|
|
398
|
+
per-entry metadata with state/TTL/staleness). Always auth-gated. Also
|
|
399
|
+
displayed on the `/runtime` dashboard page and in `eggpool runtime-status`
|
|
400
|
+
output. No API keys, auth headers, request bodies, or full URLs are exposed.
|
|
376
401
|
|
|
377
402
|
## Configuration
|
|
378
403
|
|
|
@@ -392,6 +417,7 @@ See `config.example.toml` for all available options.
|
|
|
392
417
|
- `[security]` — Allowed hosts, CORS, header redaction
|
|
393
418
|
- `[providers.*]` — Provider configurations with accounts and `routing_priority`
|
|
394
419
|
- `[proxies.*]` — Named outbound proxy definitions (pproxy URI syntax)
|
|
420
|
+
- `[network]` — Outbound client transport tuning and `[network.dns_cache]` for in-memory DNS caching (enabled by default; reduces connection latency for repeated upstream requests)
|
|
395
421
|
- `[model_overrides.*]` — Per-model protocol or path overrides
|
|
396
422
|
|
|
397
423
|
### Provider Configuration
|
|
@@ -523,6 +549,29 @@ Merge the generated provider definition into your OpenCode configuration. OpenCo
|
|
|
523
549
|
|
|
524
550
|
Model limit changes require a service restart.
|
|
525
551
|
|
|
552
|
+
### Low-wear metrics buffering
|
|
553
|
+
|
|
554
|
+
EggPool buffers lossy analytics writes (timeseries, bandwidth, token/cost aggregates) in memory and flushes them periodically to reduce microSD wear. Correctness-critical state (requests, reservations, routing) is never buffered.
|
|
555
|
+
|
|
556
|
+
Three write modes are available:
|
|
557
|
+
|
|
558
|
+
- **`immediate`** (default for debugging): existing direct-write behavior.
|
|
559
|
+
- **`balanced`** (default): buffers analytics with 30s flush intervals.
|
|
560
|
+
- **`low_wear`**: 120s flush intervals, coarser 300s buckets, 5% trace sampling, aggregate-only mode — designed for microSD / Raspberry Pi.
|
|
561
|
+
|
|
562
|
+
Example low-wear configuration:
|
|
563
|
+
|
|
564
|
+
```toml
|
|
565
|
+
[metrics]
|
|
566
|
+
write_mode = "low_wear"
|
|
567
|
+
flush_interval_s = 120
|
|
568
|
+
timeseries_bucket_s = 300
|
|
569
|
+
trace_sample_rate = 0.05
|
|
570
|
+
aggregate_only = true
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
Buffered analytics may lose at most `flush_interval_s` seconds of data after abrupt power loss. For sustained multi-session use on flash media, a high-endurance microSD or USB SSD is recommended.
|
|
574
|
+
|
|
526
575
|
## Development
|
|
527
576
|
|
|
528
577
|
```bash
|
|
@@ -556,11 +605,19 @@ src/eggpool/
|
|
|
556
605
|
├── __init__.py # Package version
|
|
557
606
|
├── __main__.py # python -m eggpool
|
|
558
607
|
├── app.py # FastAPI application factory
|
|
559
|
-
├── cli.py #
|
|
608
|
+
├── cli.py # CLI bootstrap entry point (tiny, dispatches fast-path then Click)
|
|
609
|
+
├── cli_full.py # Click CLI commands (heavy imports)
|
|
610
|
+
├── fastcli.py # Fast-path CLI (stdlib-only, croncheck/ensure-running)
|
|
560
611
|
├── auth.py # Local API key authentication
|
|
561
612
|
├── constants.py # Project-wide constants
|
|
562
613
|
├── errors.py # Exception hierarchy
|
|
563
614
|
├── logging.py # Structured logging setup
|
|
615
|
+
├── runtime.py # Process management (restart, stop, PID lifecycle)
|
|
616
|
+
├── runtime_metrics.py # Runtime/ops metrics: process, memory, DB, background tasks
|
|
617
|
+
├── runtime_dispatch.py # Bounded rolling-window dispatch overhead recorder
|
|
618
|
+
├── runtime_paths.py # PID file and log path resolution (stdlib-only)
|
|
619
|
+
├── update_checker.py # PyPI update checker (background + CLI)
|
|
620
|
+
├── cost_recompute.py # Cost recompute CLI command
|
|
564
621
|
├── onboard.py # Interactive onboarding setup
|
|
565
622
|
├── models/
|
|
566
623
|
│ ├── config.py # Pydantic config models
|
|
@@ -597,6 +654,7 @@ src/eggpool/
|
|
|
597
654
|
│ └── static/ # CSS, JavaScript, and favicon
|
|
598
655
|
├── integrations/ # External tool config generation (OpenCode, Claude Code)
|
|
599
656
|
├── security/ # Header redaction and security utilities
|
|
657
|
+
├── lifecycle/ # Backup and uninstall orchestration
|
|
600
658
|
├── deploy/ # Bundled systemd/logrotate/cron snippets for CLI output
|
|
601
659
|
└── _share/ # Bundled config examples and assets for pipx installs
|
|
602
660
|
|
|
@@ -623,7 +681,8 @@ docs/ # Documentation
|
|
|
623
681
|
├── filesystem-layout.md # Filesystem layout reference
|
|
624
682
|
├── model-limits.md # Model context limit configuration
|
|
625
683
|
├── providers.md # Provider catalog and configuration guide
|
|
626
|
-
|
|
684
|
+
├── proxy.md # Per-account outbound proxy (pproxy)
|
|
685
|
+
└── network-diagnostics.md # DNS cache and outbound client diagnostics
|
|
627
686
|
|
|
628
687
|
config-examples/ # Editor-specific config snippets
|
|
629
688
|
├── opencode.jsonc # OpenCode provider config (JSONC)
|
|
@@ -814,14 +873,32 @@ The worker is named `eggpool` in `ps` / `top` (via Granian's
|
|
|
814
873
|
|
|
815
874
|
`eggpool runtime-status` calls the local `/api/stats/runtime` endpoint and
|
|
816
875
|
prints a compact terminal summary of process topology, memory usage,
|
|
817
|
-
background task health, database file/WAL sizes,
|
|
818
|
-
counts
|
|
876
|
+
background task health, database file/WAL sizes, in-flight request
|
|
877
|
+
counts, OS load average, and the recent upstream dispatch overhead
|
|
878
|
+
distribution. Use it to diagnose daemon/systemd/cron deployments without
|
|
819
879
|
inspecting logs.
|
|
820
880
|
|
|
821
881
|
The `/api/stats/runtime` endpoint and the `/runtime` dashboard page expose
|
|
822
882
|
the same data. Both are always auth-gated regardless of `dashboard.public`
|
|
823
883
|
because they reveal operational details (PID, memory, DB path, process
|
|
824
884
|
count). The endpoint is best-effort: probes that fail on a given platform
|
|
825
|
-
(e.g., `/proc` on macOS) return `null` for
|
|
885
|
+
(e.g., `/proc` on macOS, `os.getloadavg` on Windows) return `null` for
|
|
886
|
+
the affected field.
|
|
887
|
+
|
|
888
|
+
#### Dispatch overhead
|
|
889
|
+
|
|
890
|
+
The Runtime page surfaces a `Dispatch overhead` card sourced from an
|
|
891
|
+
in-memory rolling window of the last 100 upstream attempts. It measures
|
|
892
|
+
EggPool-local pre-dispatch work (request validation, routing selection,
|
|
893
|
+
persistence, reservation accounting) and excludes provider connect/TTFT,
|
|
894
|
+
streaming, body read, and finalization. Recording happens immediately
|
|
895
|
+
before `client.send(...)` in both the streaming and non-streaming
|
|
896
|
+
dispatch paths using `time.perf_counter_ns()`, so wall-clock skew never
|
|
897
|
+
contaminates the measurement. The recorder is bounded (`deque(maxlen=100)`)
|
|
898
|
+
and thread-safe; it stores only integer nanoseconds — no request body,
|
|
899
|
+
auth header, account name, or client IP ever lands in the sample buffer.
|
|
900
|
+
The same metric is available via `/api/stats/runtime` under
|
|
901
|
+
`dispatch_overhead` (`window_size`, `sample_count`, `avg_ms`, `min_ms`,
|
|
902
|
+
`max_ms`, `p50_ms`, `p95_ms`).
|
|
826
903
|
|
|
827
904
|
See [CHANGELOG](CHANGELOG.md) for release history.
|
|
@@ -101,12 +101,25 @@ eggpool deploy cron --install
|
|
|
101
101
|
|
|
102
102
|
This writes a `@reboot` + `*/5 * * * *` `eggpool ensure-running` block to the invoking user's crontab (or `SUDO_USER`'s crontab under sudo). Use `--interval N` to change the poll cadence. The block is bracketed by `# BEGIN EggPool watchdog` / `# END EggPool watchdog` markers so uninstall only strips the eggpool-owned lines. See [docs/deployment.md](docs/deployment.md) for the full design.
|
|
103
103
|
|
|
104
|
-
### Backup and
|
|
104
|
+
### Backup and restore
|
|
105
105
|
|
|
106
|
-
EggPool
|
|
106
|
+
EggPool creates **automatic daily backups** by default. The in-process `automatic_backup` supervised task produces restore-compatible `.zip` archives every 24 hours (after an initial 5-minute startup delay) and retains the last 14. Archives are stored under `$XDG_BACKUP_HOME/eggpool` or `~/backups/eggpool`.
|
|
107
|
+
|
|
108
|
+
```toml
|
|
109
|
+
# Optional — automatic backups are enabled by default
|
|
110
|
+
[backup]
|
|
111
|
+
enabled = true
|
|
112
|
+
interval_s = 86400
|
|
113
|
+
retain_count = 14
|
|
114
|
+
startup_delay_s = 300
|
|
115
|
+
# directory = "/path/to/backups"
|
|
116
|
+
include_env = true
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Manual backup and restore are also available:
|
|
107
120
|
|
|
108
121
|
```bash
|
|
109
|
-
#
|
|
122
|
+
# Manual backup config + .env + database
|
|
110
123
|
eggpool backup
|
|
111
124
|
|
|
112
125
|
# Restore from a specific archive (or omit the path for an interactive menu)
|
|
@@ -119,7 +132,7 @@ eggpool uninstall --yes
|
|
|
119
132
|
eggpool uninstall --yes --deploy-artifacts
|
|
120
133
|
```
|
|
121
134
|
|
|
122
|
-
|
|
135
|
+
`eggpool deploy backup-cron` is optional and mainly for operators who prefer external scheduling or want backups even when the server process is not running. See [docs/backup-restore.md](docs/backup-restore.md) for the full backup/restore workflow.
|
|
123
136
|
|
|
124
137
|
## CLI Commands
|
|
125
138
|
|
|
@@ -156,7 +169,7 @@ The uninstall command removes the binary, active config, `.env`, database, and `
|
|
|
156
169
|
| `eggpool init-config` | Write bundled config.example.toml to current directory or TARGET |
|
|
157
170
|
| `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) |
|
|
158
171
|
| `eggpool deploy cron` | Print / install / uninstall the **watchdog** crontab (`@reboot` + `*/N * * * *` `ensure-running`). `--interval N` (1-59, default 5) |
|
|
159
|
-
| `eggpool deploy backup-cron` | Print / install / uninstall the daily backup cron
|
|
172
|
+
| `eggpool deploy backup-cron` | Print / install / uninstall the daily backup cron. Optional — in-process automatic backup runs by default |
|
|
160
173
|
| `eggpool deploy logrotate` | Print / install the logrotate config (validated via `logrotate -d`) |
|
|
161
174
|
| `eggpool deploy all` | Print / install systemd + logrotate + watchdog cron (backup-cron is separate) |
|
|
162
175
|
| `eggpool backup` | Create a timestamped `.zip` backup (default `~/backups/eggpool/`) |
|
|
@@ -247,7 +260,7 @@ The dashboard includes:
|
|
|
247
260
|
- Overview with request counts, error rates, costs, token usage, and a System Health row surfacing pending-request and reservation leaks
|
|
248
261
|
- Reliability page (`/reliability`) with attempt success/retry breakdown, `retry_category` distribution, pending health, and operational events
|
|
249
262
|
- Routing page (`/routing`) with per-`(model, provider)` decision aggregates, account selection counts, and exclusion taxonomy (suppressive vs advisory)
|
|
250
|
-
- Runtime page (`/runtime`) with
|
|
263
|
+
- Runtime page (`/runtime`) with RSS memory, open file descriptors, active thread count, OS load average, dispatch overhead (last 100 upstream attempts), background task status, database health, and in-flight request counts. Process count surfaces only when anomalous
|
|
251
264
|
- Traces page (`/traces`) with auth-gated recent request metadata (no error_detail, no client_ip)
|
|
252
265
|
- Account and model breakdowns with filtering, exactness columns, cache/reasoning ratios, and cost-per-1k-tokens
|
|
253
266
|
- Latency metrics including time-to-first-token (TTFT) and connect/read/coordinator-overhead phase breakdown
|
|
@@ -273,8 +286,8 @@ appropriate cache headers.
|
|
|
273
286
|
JSON stats endpoints are available under `/api/stats/*`, including summary,
|
|
274
287
|
accounts, models, timeseries, errors, latency, pings, bandwidth, attempts,
|
|
275
288
|
retries, routing, routing-selections, routing-exclusions, operational,
|
|
276
|
-
pending-health, runtime, recent-requests, recent/{request_id},
|
|
277
|
-
`/api/events`. The recent-requests, recent/{request_id}, pending-health,
|
|
289
|
+
pending-health, runtime, recent-requests, recent/{request_id},
|
|
290
|
+
`/api/stats/update`, and `/api/events`. The recent-requests, recent/{request_id}, pending-health,
|
|
278
291
|
and runtime endpoints are always auth-gated (even when the dashboard is
|
|
279
292
|
public) because they expose per-request metadata (model, prompt volume,
|
|
280
293
|
error class), operational state (pending reservations, reserved cost),
|
|
@@ -329,6 +342,18 @@ dashboard's public/auth setting.
|
|
|
329
342
|
`source_confidence` (exact external ID vs. curated alias vs. unknown).
|
|
330
343
|
Used by the dashboard to render the cost-exactness badge and the
|
|
331
344
|
high-spend estimated warning.
|
|
345
|
+
- **Update checker** (`/api/stats/update`): the server checks PyPI at startup
|
|
346
|
+
and approximately every 24 hours for newer eggpool releases. When an update
|
|
347
|
+
is found, the dashboard footer shows a non-intrusive indicator with the
|
|
348
|
+
current and latest versions and a copyable `eggpool update` command. The
|
|
349
|
+
JSON snapshot is also available at `GET /api/stats/update`.
|
|
350
|
+
- **Network diagnostics** (`/api/network/diagnostics`): sanitized outbound
|
|
351
|
+
client lifecycle (build count, request count, error count, per-scope builds,
|
|
352
|
+
per-host requests/errors) and DNS cache behavior (hits, misses, negative
|
|
353
|
+
hits, stale hits, evictions, per-host breakdown, resolution errors,
|
|
354
|
+
per-entry metadata with state/TTL/staleness). Always auth-gated. Also
|
|
355
|
+
displayed on the `/runtime` dashboard page and in `eggpool runtime-status`
|
|
356
|
+
output. No API keys, auth headers, request bodies, or full URLs are exposed.
|
|
332
357
|
|
|
333
358
|
## Configuration
|
|
334
359
|
|
|
@@ -348,6 +373,7 @@ See `config.example.toml` for all available options.
|
|
|
348
373
|
- `[security]` — Allowed hosts, CORS, header redaction
|
|
349
374
|
- `[providers.*]` — Provider configurations with accounts and `routing_priority`
|
|
350
375
|
- `[proxies.*]` — Named outbound proxy definitions (pproxy URI syntax)
|
|
376
|
+
- `[network]` — Outbound client transport tuning and `[network.dns_cache]` for in-memory DNS caching (enabled by default; reduces connection latency for repeated upstream requests)
|
|
351
377
|
- `[model_overrides.*]` — Per-model protocol or path overrides
|
|
352
378
|
|
|
353
379
|
### Provider Configuration
|
|
@@ -479,6 +505,29 @@ Merge the generated provider definition into your OpenCode configuration. OpenCo
|
|
|
479
505
|
|
|
480
506
|
Model limit changes require a service restart.
|
|
481
507
|
|
|
508
|
+
### Low-wear metrics buffering
|
|
509
|
+
|
|
510
|
+
EggPool buffers lossy analytics writes (timeseries, bandwidth, token/cost aggregates) in memory and flushes them periodically to reduce microSD wear. Correctness-critical state (requests, reservations, routing) is never buffered.
|
|
511
|
+
|
|
512
|
+
Three write modes are available:
|
|
513
|
+
|
|
514
|
+
- **`immediate`** (default for debugging): existing direct-write behavior.
|
|
515
|
+
- **`balanced`** (default): buffers analytics with 30s flush intervals.
|
|
516
|
+
- **`low_wear`**: 120s flush intervals, coarser 300s buckets, 5% trace sampling, aggregate-only mode — designed for microSD / Raspberry Pi.
|
|
517
|
+
|
|
518
|
+
Example low-wear configuration:
|
|
519
|
+
|
|
520
|
+
```toml
|
|
521
|
+
[metrics]
|
|
522
|
+
write_mode = "low_wear"
|
|
523
|
+
flush_interval_s = 120
|
|
524
|
+
timeseries_bucket_s = 300
|
|
525
|
+
trace_sample_rate = 0.05
|
|
526
|
+
aggregate_only = true
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
Buffered analytics may lose at most `flush_interval_s` seconds of data after abrupt power loss. For sustained multi-session use on flash media, a high-endurance microSD or USB SSD is recommended.
|
|
530
|
+
|
|
482
531
|
## Development
|
|
483
532
|
|
|
484
533
|
```bash
|
|
@@ -512,11 +561,19 @@ src/eggpool/
|
|
|
512
561
|
├── __init__.py # Package version
|
|
513
562
|
├── __main__.py # python -m eggpool
|
|
514
563
|
├── app.py # FastAPI application factory
|
|
515
|
-
├── cli.py #
|
|
564
|
+
├── cli.py # CLI bootstrap entry point (tiny, dispatches fast-path then Click)
|
|
565
|
+
├── cli_full.py # Click CLI commands (heavy imports)
|
|
566
|
+
├── fastcli.py # Fast-path CLI (stdlib-only, croncheck/ensure-running)
|
|
516
567
|
├── auth.py # Local API key authentication
|
|
517
568
|
├── constants.py # Project-wide constants
|
|
518
569
|
├── errors.py # Exception hierarchy
|
|
519
570
|
├── logging.py # Structured logging setup
|
|
571
|
+
├── runtime.py # Process management (restart, stop, PID lifecycle)
|
|
572
|
+
├── runtime_metrics.py # Runtime/ops metrics: process, memory, DB, background tasks
|
|
573
|
+
├── runtime_dispatch.py # Bounded rolling-window dispatch overhead recorder
|
|
574
|
+
├── runtime_paths.py # PID file and log path resolution (stdlib-only)
|
|
575
|
+
├── update_checker.py # PyPI update checker (background + CLI)
|
|
576
|
+
├── cost_recompute.py # Cost recompute CLI command
|
|
520
577
|
├── onboard.py # Interactive onboarding setup
|
|
521
578
|
├── models/
|
|
522
579
|
│ ├── config.py # Pydantic config models
|
|
@@ -553,6 +610,7 @@ src/eggpool/
|
|
|
553
610
|
│ └── static/ # CSS, JavaScript, and favicon
|
|
554
611
|
├── integrations/ # External tool config generation (OpenCode, Claude Code)
|
|
555
612
|
├── security/ # Header redaction and security utilities
|
|
613
|
+
├── lifecycle/ # Backup and uninstall orchestration
|
|
556
614
|
├── deploy/ # Bundled systemd/logrotate/cron snippets for CLI output
|
|
557
615
|
└── _share/ # Bundled config examples and assets for pipx installs
|
|
558
616
|
|
|
@@ -579,7 +637,8 @@ docs/ # Documentation
|
|
|
579
637
|
├── filesystem-layout.md # Filesystem layout reference
|
|
580
638
|
├── model-limits.md # Model context limit configuration
|
|
581
639
|
├── providers.md # Provider catalog and configuration guide
|
|
582
|
-
|
|
640
|
+
├── proxy.md # Per-account outbound proxy (pproxy)
|
|
641
|
+
└── network-diagnostics.md # DNS cache and outbound client diagnostics
|
|
583
642
|
|
|
584
643
|
config-examples/ # Editor-specific config snippets
|
|
585
644
|
├── opencode.jsonc # OpenCode provider config (JSONC)
|
|
@@ -770,14 +829,32 @@ The worker is named `eggpool` in `ps` / `top` (via Granian's
|
|
|
770
829
|
|
|
771
830
|
`eggpool runtime-status` calls the local `/api/stats/runtime` endpoint and
|
|
772
831
|
prints a compact terminal summary of process topology, memory usage,
|
|
773
|
-
background task health, database file/WAL sizes,
|
|
774
|
-
counts
|
|
832
|
+
background task health, database file/WAL sizes, in-flight request
|
|
833
|
+
counts, OS load average, and the recent upstream dispatch overhead
|
|
834
|
+
distribution. Use it to diagnose daemon/systemd/cron deployments without
|
|
775
835
|
inspecting logs.
|
|
776
836
|
|
|
777
837
|
The `/api/stats/runtime` endpoint and the `/runtime` dashboard page expose
|
|
778
838
|
the same data. Both are always auth-gated regardless of `dashboard.public`
|
|
779
839
|
because they reveal operational details (PID, memory, DB path, process
|
|
780
840
|
count). The endpoint is best-effort: probes that fail on a given platform
|
|
781
|
-
(e.g., `/proc` on macOS) return `null` for
|
|
841
|
+
(e.g., `/proc` on macOS, `os.getloadavg` on Windows) return `null` for
|
|
842
|
+
the affected field.
|
|
843
|
+
|
|
844
|
+
#### Dispatch overhead
|
|
845
|
+
|
|
846
|
+
The Runtime page surfaces a `Dispatch overhead` card sourced from an
|
|
847
|
+
in-memory rolling window of the last 100 upstream attempts. It measures
|
|
848
|
+
EggPool-local pre-dispatch work (request validation, routing selection,
|
|
849
|
+
persistence, reservation accounting) and excludes provider connect/TTFT,
|
|
850
|
+
streaming, body read, and finalization. Recording happens immediately
|
|
851
|
+
before `client.send(...)` in both the streaming and non-streaming
|
|
852
|
+
dispatch paths using `time.perf_counter_ns()`, so wall-clock skew never
|
|
853
|
+
contaminates the measurement. The recorder is bounded (`deque(maxlen=100)`)
|
|
854
|
+
and thread-safe; it stores only integer nanoseconds — no request body,
|
|
855
|
+
auth header, account name, or client IP ever lands in the sample buffer.
|
|
856
|
+
The same metric is available via `/api/stats/runtime` under
|
|
857
|
+
`dispatch_overhead` (`window_size`, `sample_count`, `avg_ms`, `min_ms`,
|
|
858
|
+
`max_ms`, `p50_ms`, `p95_ms`).
|
|
782
859
|
|
|
783
860
|
See [CHANGELOG](CHANGELOG.md) for release history.
|
|
@@ -23,13 +23,21 @@ src/eggpool/
|
|
|
23
23
|
├── routing/ # Quota-aware routing, eligibility, provider parsing
|
|
24
24
|
├── security/ # Header redaction, security utilities
|
|
25
25
|
├── stats/ # Statistics queries and service
|
|
26
|
+
├── lifecycle/ # Backup and uninstall orchestration
|
|
26
27
|
├── deploy/ # Bundled systemd/logrotate/cron snippets for CLI output
|
|
27
28
|
├── _share/ # Bundled config examples and assets for pipx installs
|
|
28
29
|
├── auth.py # Local API key authentication (constant-time)
|
|
29
|
-
├── cli.py #
|
|
30
|
+
├── cli.py # CLI bootstrap entry point (tiny, dispatches fast-path then Click)
|
|
31
|
+
├── cli_full.py # Click CLI commands (heavy imports)
|
|
32
|
+
├── fastcli.py # Fast-path CLI (stdlib-only, croncheck/ensure-running)
|
|
30
33
|
├── errors.py # Exception hierarchy
|
|
31
34
|
├── logging.py # Structured logging setup
|
|
32
|
-
├──
|
|
35
|
+
├── runtime.py # Process management (restart, stop, PID lifecycle)
|
|
36
|
+
├── runtime_metrics.py # Runtime/ops metrics: process, memory, DB, background tasks, OS load average
|
|
37
|
+
├── runtime_dispatch.py # Bounded rolling-window recorder for EggPool-local upstream dispatch overhead
|
|
38
|
+
├── runtime_paths.py # PID file and log path resolution (stdlib-only)
|
|
39
|
+
├── update_checker.py # PyPI update checker (background + CLI)
|
|
40
|
+
├── cost_recompute.py # Cost recompute CLI command
|
|
33
41
|
└── constants.py # Project-wide constants
|
|
34
42
|
```
|
|
35
43
|
|
|
@@ -54,7 +62,7 @@ Key invariants:
|
|
|
54
62
|
- Each attempt reservation is released exactly once via `AttemptFinalizer`
|
|
55
63
|
- The same URL composition rules apply to catalog fetch and chat dispatch
|
|
56
64
|
- **Structured observability persistence (migrations 0026-0029)** every `request_attempts` row carries provider/model/protocol/retry_category/latency/bytes/streamed/is_retry_outcome; every routing decision is persisted to `routing_decisions` in the same transaction as the `request_attempts` INSERT; safety-net tasks (`_crash_recovery`, `_finalize_stale_requests_once`, `reconcile_expired_reservations`) record `operational_events` rows inside the same transaction as the durable state mutation; latency is decomposed into `upstream_connect_ms / upstream_read_ms / coordinator_overhead_ms` so the dashboard can distinguish network vs upstream vs eggpool-side bottlenecks
|
|
57
|
-
- **Runtime metrics are best-effort and process-local** — the `/api/stats/runtime` endpoint and `eggpool runtime-status` CLI command gather process topology, memory, background task state,
|
|
65
|
+
- **Runtime metrics are best-effort and process-local** — the `/api/stats/runtime` endpoint and `eggpool runtime-status` CLI command gather process topology, memory, background task state, database health, OS load average (`os.getloadavg` + normalized per-core), and a bounded rolling-window dispatch-overhead distribution via `DispatchOverheadRecorder` (`src/eggpool/runtime_dispatch.py`); failed probes return `null` rather than raising, and the endpoint is always auth-gated even with a public dashboard
|
|
58
66
|
|
|
59
67
|
## Multi-Provider Architecture
|
|
60
68
|
|
|
@@ -62,7 +70,7 @@ EggPool supports 27+ upstream providers (OpenCode Go, OpenAI, Anthropic, Groq, D
|
|
|
62
70
|
|
|
63
71
|
### MiniMax templates
|
|
64
72
|
|
|
65
|
-
- **`minimax`** — international host `https://api.minimax.io/anthropic`. Anthropic-compatible transport (key sent as `x-api-key` plus `anthropic-version: 2023-06-01`). Model listing
|
|
73
|
+
- **`minimax`** — international host `https://api.minimax.io/anthropic`. Anthropic-compatible transport (key sent as `x-api-key` plus `anthropic-version: 2023-06-01`). Model listing uses live discovery via `/v1/models` endpoint; static `[[providers.minimax.static_models]]` rows serve as fallback only. The Anthropic model-list normalizer auto-detects MiniMax's response shape. Default for keys from `minimax.io`.
|
|
66
74
|
- **`minimax-cn`** — China host `https://api.minimaxi.com/v1` with the same OpenAI paths as a standard provider. Live verification is required because the China endpoint family has not been confirmed against EggPool's Anthropic-compatible transport.
|
|
67
75
|
|
|
68
76
|
The stored key must be the raw token; EggPool prepends the configured auth scheme automatically. An optional `[providers.<id>.verify]` block lets the verifier know which model to probe when neither `--openai-model` nor `--anthropic-model` is passed on the CLI.
|
|
@@ -129,7 +137,7 @@ SQLite via aiosqlite with WAL mode. Single-connection serialization via a lock +
|
|
|
129
137
|
|
|
130
138
|
### Schema Migrations
|
|
131
139
|
|
|
132
|
-
Ordered SQL migrations in `db/schema/` (0001 through
|
|
140
|
+
Ordered SQL migrations in `db/schema/` (0001 through 0032). Checksums tracked in `checksums.json`.
|
|
133
141
|
|
|
134
142
|
### Repositories
|
|
135
143
|
|