eggpool 0.6.2__tar.gz → 0.6.3__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.6.2 → eggpool-0.6.3}/AGENTS.md +1 -1
- {eggpool-0.6.2 → eggpool-0.6.3}/PKG-INFO +1 -1
- {eggpool-0.6.2 → eggpool-0.6.3}/architecture/README.md +4 -4
- {eggpool-0.6.2 → eggpool-0.6.3}/config.example.toml +1 -3
- {eggpool-0.6.2 → eggpool-0.6.3}/docs/deployment.md +6 -6
- {eggpool-0.6.2 → eggpool-0.6.3}/docs/opencode-stream-stability.md +5 -5
- {eggpool-0.6.2 → eggpool-0.6.3}/docs/providers.md +6 -5
- {eggpool-0.6.2 → eggpool-0.6.3}/docs/raspberry-pi.md +6 -7
- {eggpool-0.6.2 → eggpool-0.6.3}/pyproject.toml +1 -1
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/_share/config.example.toml +3 -3
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/app.py +9 -3
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/routes.py +136 -80
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/static/dashboard.js +2 -2
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/model_info/service.py +63 -20
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/model_info/sources/openrouter.py +130 -1
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/models/config.py +4 -4
- {eggpool-0.6.2 → eggpool-0.6.3}/.env.example +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/.github/workflows/ci.yml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/.github/workflows/release.yml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/.gitignore +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/CHANGELOG.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/LICENSE +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/README.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/config-examples/claude-code.env +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/config-examples/opencode.jsonc +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/deploy/eggpool-logrotate.conf +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/deploy/eggpool.service +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/deploy/env.example +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/docs/backup-restore.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/docs/cache-compression-profiles.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/docs/cache-compression-troubleshooting.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/docs/cache-compression.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/docs/filesystem-layout.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/docs/firewall.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/docs/model-info-openrouter-debug.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/docs/model-limits.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/docs/network-diagnostics.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/docs/proxy.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/docs/thinking.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/docs/transcoding.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/scripts/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/scripts/check_database.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/scripts/debug_model_info_openrouter.sh +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/scripts/install.sh +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/scripts/install_prompt.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/scripts/repro_high_concurrency_streams.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/scripts/smoke_test.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/scripts/test_model_info_identity.sh +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/scripts/validate_routing.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/scripts/verify_upstream_auth.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/__main__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/_share/.env.example +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/accounts/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/accounts/registry.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/accounts/state.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/api/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/api/backoff.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/api/chat_completions.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/api/errors.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/api/messages.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/api/model_info.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/api/models.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/api/network.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/api/proxy_request.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/api/runtime.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/api/stats.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/api/update.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/auth.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/background/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/background/backup.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/background/cleanup.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/catalog/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/catalog/cache.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/catalog/capabilities.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/catalog/catalog_resolvers.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/catalog/fetcher.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/catalog/limits.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/catalog/models_dev.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/catalog/normalizer.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/catalog/pricing.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/catalog/pricing_aliases.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/catalog/pricing_resolver.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/catalog/protocols.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/catalog/service.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/cli.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/cli_full.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/config.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/config_utils.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/constants.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/cost_recompute.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/cost_repair.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/_resources.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/escape.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/render.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/static/chart.umd.min.js +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/static/dashboard.css +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/static/favicon.svg +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/telemetry.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/theme.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Booberry.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Catppuccin Latte.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Catppuccin Macchiato.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Catppuccin Mocha.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Cyber Red.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Cyberpunk.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Dark Green.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Discord (80_ Saturation).toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Discord.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Dracula.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Ferra Light.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Flexor Dark.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Gruvbox.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Halcyon Dark.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/IntelliJ Light.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Kanagawa.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Macaw Dark.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Macaw Light.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Matrix.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Noctis Lilac.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Nord.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Nostromo Terminal.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/One Dark.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Oxocarbon.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Rose Pine Dawn.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Rose Pine Moon.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Rose Pine.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Solarized Dark.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Sonokai.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Tokyo Night Storm.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/VESPER.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/Zenburn.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/acton.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/bam.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/base16-atelier-forest-light.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/berlin.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/black but with important highlights.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/broc.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/cork.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/ferra.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/forest.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/lisbon.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/midnight.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/oslo.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/plum.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/portland.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/sunset.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/tofino.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/vanimo.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/dashboard/themes/vik.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/connection.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/migrations.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/repositories.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/rollup_repository.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0001_initial.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0002_indexes.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0003_request_attempts.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0004_integration_hardening.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0005_price_microdollars.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0006_correct_price_microdollars.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0007_price_cache_rates.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0008_proxy_request_identity.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0009_model_protocol_source.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0010_health_probe.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0011_model_resolution_status.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0012_drop_reservations_estimated_microdollars.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0013_request_attempts_account_id_index.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0014_bandwidth_tracking.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0015_multi_provider.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0016_requests_provider_id.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0017_price_snapshots_provider_id.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0018_provider_pings.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0019_client_ip.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0020_performance_indexes.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0021_provider_model_metadata.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0022_dashboard_indexes.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0023_deprecated_model_placeholder.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0024_account_backoffs.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0025_stale_request_index.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0026_attempt_observability.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0027_routing_decisions.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0028_operational_events.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0029_latency_phases.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0030_model_pricing_aliases.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0031_price_snapshot_provenance.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0032_usage_rollups.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0033_request_provider_local_cost.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0034_transcoding_daily.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0035_routing_decision_score_components.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0036_model_info.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0037_model_info_failure_count.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0038_model_info_phase5.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0039_thinking_observability.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0040_cache_token_observability.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0041_canonical_request_segmentation.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0042_observe_mode_compression_accounting.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0043_safe_suffix_compression.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0044_compression_policy_resolver.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0045_synthetic_cache_controls.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0046_closed_loop_threshold_tuning.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0047_normalize_rollup_bucket_start.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0048_request_cost_repairs.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/0049_model_info_match_evidence.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/db/schema/checksums.json +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/deploy/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/deploy_user.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/errors.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/fastcli.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/health/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/health/backoff.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/health/circuit_breaker.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/health/health_manager.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/integrations/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/integrations/aider.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/integrations/cline.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/integrations/codex.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/integrations/common.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/integrations/continue_dev.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/integrations/goose.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/integrations/kilo.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/integrations/opencode.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/integrations/openhands.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/integrations/qwen_code.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/integrations/roo_code.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/jsonx.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/lifecycle/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/lifecycle/backup.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/lifecycle/uninstall.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/logging.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/metrics/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/metrics/buffer.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/metrics/thinking.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/model_info/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/model_info/dedup.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/model_info/identity.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/model_info/matching.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/model_info/normalization.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/model_info/presentation.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/model_info/repository.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/model_info/scheduler.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/model_info/sources/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/model_info/sources/artificial_analysis.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/model_info/sources/base.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/model_info/sources/huggingface.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/model_info/sources/provider_catalog.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/model_info/types.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/models/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/models/api.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/models/database.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/models/domain.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/onboard.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/providers/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/providers/_templates.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/providers/auth.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/providers/client_pool.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/providers/connect.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/providers/contract.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/providers/dns_cache.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/providers/outbound.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/providers/pproxy_transport.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/proxy/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/proxy/client.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/proxy/cost_reporting.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/proxy/normalized_usage.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/proxy/sse_observer.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/proxy/usage.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/py.typed +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/quota/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/quota/audit.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/quota/estimation.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/quota/reservation.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/quota/scorer.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/request/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/request/attempt_finalizer.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/request/body.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/request/coordinator.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/request/finalization_queue.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/request/finalizer.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/request/limits.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/request/routing_trace_guard.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/request/stream_diagnostics.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/retry/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/retry/classification.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/routing/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/routing/config.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/routing/eligibility.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/routing/fairness.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/routing/provider.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/routing/router.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/runtime.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/runtime_dispatch.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/runtime_metrics.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/runtime_paths.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/security/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/security/redaction.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/stats/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/stats/cache_metrics.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/stats/dashboard_explain.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/stats/grouped_timeseries.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/stats/queries.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/stats/segmentation.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/stats/service.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/stats/transcoding.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/toml_edit.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/anthropic_to_openai.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/budget_resolver.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/cache_stability.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/cache_synthesis.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/cache_synthesis_policy.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/compression/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/compression/analyzer.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/compression/apply.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/compression/markers.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/compression/policy.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/compression/policy_resolver.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/compression/tuning.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/context.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/errors.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/ids.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/json_helpers.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/openai_to_anthropic.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/policy.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/prepared.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/protocol.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/segmentation.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/segmentation_guard.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/static_headers.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/streaming.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/transcoder/usage.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/src/eggpool/update_checker.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.3}/uv.lock +0 -0
|
@@ -143,7 +143,7 @@ CI sets `PYTHONHASHSEED=0` and `TZ=UTC`; reproduce locally for deterministic res
|
|
|
143
143
|
- **Database invariants**: SQLite WAL, single-connection serialization, `async with db.transaction():` for all DML.
|
|
144
144
|
- **Quota and routing**: tier-based routing via `routing_priority`, `QuotaFairScorer`, upstream-authoritative suppression, same-tier fairness rotor.
|
|
145
145
|
- **Error hierarchy**: `AggregatorError` → `UpstreamError` → specific subclasses. `CapabilityError` for thinking/reasoning capability mismatches. `TranscodeLossError` (HTTP 400) for loss-policy reject. `ProtocolMismatchError` for endpoint/model-protocol mismatches.
|
|
146
|
-
- **Process model**: supervisor + Granian worker (`workers=1`), PID file lifecycle, daemon mode (default for `eggpool serve`; `--verbose` for foreground). Default `runtime_threads=
|
|
146
|
+
- **Process model**: supervisor + Granian worker (`workers=1`), PID file lifecycle, daemon mode (default for `eggpool serve`; `--verbose` for foreground). Default `runtime_threads=4`, `database_worker_threads=2` (separate read-only stats connection). Startup logs `Granian profile: workers=1 runtime_threads=N database_worker_threads=M access_log=...`.
|
|
147
147
|
- **Dashboard**: server-rendered HTML, Chart.js v4, grouped timeseries, CSS tooltips. Dashboard HTML pages use a 30s in-memory cache (`DashboardTelemetry`) for heavy aggregate queries. Telemetry exposes p50/p95 render times and slowest route under `/api/stats/runtime`. Progressive graph hydration (Phase 7) decouples page shell render from chart data: chart pages render a loading shell and hydrate via `data-chart-endpoint` after the shell arrives, with per-namespace/per-period cache TTLs (1h=15s, 24h=30-60s, 7d=120-240s, 30d=240-300s) and rollup-first query behavior suppressing raw fallback for windows >2h. Overview token row uses three explicit cards (`Accounted tokens`, `Fresh tokens`, `Provider cache hit rate`) so cache-heavy workloads (`cache_read > fresh_tokens`) cannot look like an arithmetic bug; the headline `Accounted tokens` card surfaces `input + output + cache_read + cache_write` while `Fresh tokens` keeps the legacy `input + output` semantics. The `Provider cache hit rate` card uses protocol-aware canonical terms: `cache_read_tokens_canonical` (hits), `cache_write_tokens_canonical` (warmup, not hits), and `cache_eligible_input_tokens` (denominator). Per-protocol denominator rules: OpenAI-compatible input is total-billed prompt tokens (`prompt_tokens`); Anthropic denominator is `input_tokens + cache_read + cache_creation` because Anthropic `input_tokens` is fresh-only. Cache writes/creation are never described as hits. Cache read share stays bounded to `cache_read / (input + cache_read + cache_write)`. Stats summary payload exposes `total_tokens` (legacy fresh-token volume, kept for API backward compatibility), `fresh_tokens`, and `accounted_tokens` — the dashboard uses `accounted_tokens` without changing the `total_tokens` API value. Dashboard `/models` page joins canonical model-info summaries to rendered rows via `_normalize_dashboard_model_row()` (splitting provider-suffixed `model_id` into `base_model_id`/`provider_id`/`_model_info_lookup_id`) and surfaces join-failure diagnostics through `ModelInfoDashboardState.matched_row_count` / `unmatched_sample`. `CatalogRowsState` mirrors `ModelInfoDashboardState` for the catalog row builder; the previously-silent `except Exception: return []` blocks now log the traceback and set `degraded_reason="fetch_error"`. The catalog accessor `ModelCatalogCache.get_provider_model_entries()` is the source of truth for the provider-scoped `/models` rows: returns a deterministic `dict[(model_id, provider_id), dict]`, excludes the deprecated placeholder, applies configured capability overrides when `_config` is attached, and emits shallow copies so callers cannot mutate the cache. Unresolved rows (`protocol=None`) are kept so the dashboard can render them as `available=False, catalog_status="unavailable"` rather than silently dropping them.
|
|
148
148
|
- **Model capabilities**: protocol-neutral `ThinkingCapability` / `ModelCapabilities` with deterministic merge. Config overrides at `[model_capabilities."<id>".thinking]` and per-provider scoped.
|
|
149
149
|
- **Catalog refresh**: non-destructive by default; destructive withdrawal gated on `authoritative=True AND allow_withdrawals=True`. `static_models` is the source of truth for provider-specific protocol.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: eggpool
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
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
|
|
@@ -2693,14 +2693,14 @@ Granular dashboard handlers touched: `handle_runtime`, `handle_cache`, `handle_t
|
|
|
2693
2693
|
|
|
2694
2694
|
#### Granian Runtime Threads
|
|
2695
2695
|
|
|
2696
|
-
`ServerConfig.threads` defaults to `
|
|
2696
|
+
`ServerConfig.threads` defaults to `4` (was `2`). The default config example now exposes the knob:
|
|
2697
2697
|
|
|
2698
2698
|
```toml
|
|
2699
2699
|
[server]
|
|
2700
|
-
threads =
|
|
2700
|
+
threads = 4
|
|
2701
2701
|
```
|
|
2702
2702
|
|
|
2703
|
-
Granian still passes `workers=1`.
|
|
2703
|
+
Granian still passes `workers=1`. Four runtime threads let a single worker multiplex streaming proxy traffic, dashboard requests, and lightweight background work without the single-event-loop starvation that becomes visible when long-lived SSE streams block the loop. `1` remains the documented minimum-footprint override for extremely constrained devices. Startup logs the effective profile:
|
|
2704
2704
|
|
|
2705
2705
|
```text
|
|
2706
2706
|
Granian profile: workers=1 runtime_threads=N database_worker_threads=M access_log=...
|
|
@@ -2945,7 +2945,7 @@ CLI reproducer.
|
|
|
2945
2945
|
### Critical rules
|
|
2946
2946
|
|
|
2947
2947
|
- Do not raise `server.threads` or `workers` to "fix" high-concurrency
|
|
2948
|
-
stream instability. Granian runs `workers=1` and `threads=
|
|
2948
|
+
stream instability. Granian runs `workers=1` and `threads=4`; raising
|
|
2949
2949
|
`threads` does not improve HTTPX concurrency and raising `workers`
|
|
2950
2950
|
multiplies the SQLite connection budget.
|
|
2951
2951
|
- Keep `database.worker_threads = 2` (default) so the dashboard
|
|
@@ -5,9 +5,7 @@ port = 11300
|
|
|
5
5
|
log_level = "INFO"
|
|
6
6
|
access_log = true
|
|
7
7
|
# Granian runtime threads inside the single worker process. Keep workers=1.
|
|
8
|
-
|
|
9
|
-
# and other SBC installs that serve dashboard traffic during active proxy use.
|
|
10
|
-
threads = 2
|
|
8
|
+
threads = 4
|
|
11
9
|
|
|
12
10
|
[upstream]
|
|
13
11
|
base_url = "https://opencode.ai/zen/go/v1"
|
|
@@ -317,7 +317,7 @@ dashboard responsiveness under request load.
|
|
|
317
317
|
|
|
318
318
|
```toml
|
|
319
319
|
[server]
|
|
320
|
-
threads =
|
|
320
|
+
threads = 4
|
|
321
321
|
access_log = false
|
|
322
322
|
|
|
323
323
|
[database]
|
|
@@ -403,7 +403,7 @@ and restart. Revert to `"sampled"` or `"off"` when done.
|
|
|
403
403
|
| Symptom | Likely cause | Knob to adjust |
|
|
404
404
|
|---|---|---|
|
|
405
405
|
| Dashboard loads slowly under request load | DB lock contention | Set `database.worker_threads = 2` |
|
|
406
|
-
| Dashboard still slow | Runtime thread starvation |
|
|
406
|
+
| Dashboard still slow | Runtime thread starvation | Check dashboard telemetry, then tune `server.threads` above 4 only on capable hardware |
|
|
407
407
|
| High write volume / microSD wear | Routing trace writes | Set `routing.trace.mode = "sampled"` |
|
|
408
408
|
| Background tasks cluster at minute boundaries | Task scheduling | Default is staggered; check `initial_delay_s` |
|
|
409
409
|
| Stale dashboard data | Cache TTL | Wait 30s or check dashboard cache settings |
|
|
@@ -565,11 +565,11 @@ address for the probe). Either a live PID or a 200 from the probe
|
|
|
565
565
|
causes the new `serve` to exit non-zero so a stale PID file is
|
|
566
566
|
never silently overwritten.
|
|
567
567
|
|
|
568
|
-
The primary tuning knob is `[server].threads` (int, default `
|
|
568
|
+
The primary tuning knob is `[server].threads` (int, default `4`,
|
|
569
569
|
min `1`, max `64`), which sets Granian `runtime_threads` — the
|
|
570
|
-
number of event-loop threads in the worker. The default of
|
|
571
|
-
threads
|
|
572
|
-
|
|
570
|
+
number of event-loop threads in the worker. The default of four
|
|
571
|
+
threads lets the single Granian worker multiplex streaming proxy
|
|
572
|
+
traffic + dashboard requests
|
|
573
573
|
without single-event-loop starvation. Set `threads = 1` for
|
|
574
574
|
extremely constrained devices, or raise it on capable hardware:
|
|
575
575
|
|
|
@@ -103,10 +103,10 @@ For OpenCode-style workloads, budget these limits:
|
|
|
103
103
|
- `database.worker_threads`: 2 is sufficient for dashboards; raise
|
|
104
104
|
to 4 if the dashboard itself lags.
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
HTTPX
|
|
109
|
-
budget per upstream IP.
|
|
106
|
+
Keep `server.threads` bounded and leave `workers=1`. The default of
|
|
107
|
+
4 helps the single worker multiplex dashboard work and active streams;
|
|
108
|
+
it does not increase HTTPX pool capacity. Adding workers multiplies the
|
|
109
|
+
connection budget per upstream IP.
|
|
110
110
|
|
|
111
111
|
## Closure validation
|
|
112
112
|
|
|
@@ -151,4 +151,4 @@ The `/api/stats/runtime` endpoint exposes these stream-stability sections:
|
|
|
151
151
|
- `stream_diagnostics` — outcome counters and histograms
|
|
152
152
|
- `finalization_retry_queue` — queue depth, drain stats, overflow/duplicate counters
|
|
153
153
|
- `routing_trace_guard` — skip rate and lock-pressure threshold
|
|
154
|
-
- `db.contention` — lock-wait p50/p95/p99/max and sample count
|
|
154
|
+
- `db.contention` — lock-wait p50/p95/p99/max and sample count
|
|
@@ -445,7 +445,7 @@ state:
|
|
|
445
445
|
|
|
446
446
|
```toml
|
|
447
447
|
[server]
|
|
448
|
-
threads =
|
|
448
|
+
threads = 4
|
|
449
449
|
|
|
450
450
|
[database]
|
|
451
451
|
worker_threads = 2
|
|
@@ -468,7 +468,7 @@ read timeout to absorb slow model first-token latencies:
|
|
|
468
468
|
|
|
469
469
|
```toml
|
|
470
470
|
[server]
|
|
471
|
-
threads =
|
|
471
|
+
threads = 4
|
|
472
472
|
|
|
473
473
|
[database]
|
|
474
474
|
worker_threads = 2
|
|
@@ -482,9 +482,10 @@ write_timeout_s = 30
|
|
|
482
482
|
pool_timeout_s = 60
|
|
483
483
|
```
|
|
484
484
|
|
|
485
|
-
Keep `server.threads`
|
|
486
|
-
|
|
487
|
-
real lever is
|
|
485
|
+
Keep `server.threads` bounded — it helps the single worker multiplex
|
|
486
|
+
dashboard work alongside active streams, but it does not raise HTTPX
|
|
487
|
+
pool capacity. The real upstream-concurrency lever is
|
|
488
|
+
`max_connections`.
|
|
488
489
|
|
|
489
490
|
### Diagnostic low-noise mode
|
|
490
491
|
|
|
@@ -65,7 +65,7 @@ explicitly set the recommended profile:
|
|
|
65
65
|
|
|
66
66
|
```toml
|
|
67
67
|
[server]
|
|
68
|
-
threads =
|
|
68
|
+
threads = 4 # runtime threads for dashboard responsiveness
|
|
69
69
|
access_log = false # optional: reduce I/O noise after initial setup
|
|
70
70
|
|
|
71
71
|
[database]
|
|
@@ -100,20 +100,19 @@ include_score_components = false
|
|
|
100
100
|
## Process Model
|
|
101
101
|
|
|
102
102
|
EggPool's default process model is Pi-friendly: one `eggpool serve`
|
|
103
|
-
supervisor process plus one Granian worker, with
|
|
103
|
+
supervisor process plus one Granian worker, with four event-loop
|
|
104
104
|
threads in the worker (configurable via `[server].threads`). Both
|
|
105
105
|
processes appear as `eggpool` in `ps` / `top` (no generic `python`
|
|
106
|
-
entry), so the total footprint is two processes and
|
|
106
|
+
entry), so the total footprint is two processes and four runtime threads before
|
|
107
107
|
considering any upstream outbound connections.
|
|
108
108
|
|
|
109
109
|
The single tuning knob for per-worker concurrency is `[server].threads`
|
|
110
|
-
(int, default `
|
|
111
|
-
|
|
112
|
-
genuinely needs more concurrency than two event loops can deliver:
|
|
110
|
+
(int, default `4`, max `64`), which maps to Granian `runtime_threads`.
|
|
111
|
+
Set it to `1` for minimum footprint or raise it on capable hardware:
|
|
113
112
|
|
|
114
113
|
```toml
|
|
115
114
|
[server]
|
|
116
|
-
threads =
|
|
115
|
+
threads = 4
|
|
117
116
|
```
|
|
118
117
|
|
|
119
118
|
The PID file path is resolved by `eggpool.runtime_paths.default_pid_file()` in this precedence: `$EGGPOOL_PID_FILE` → `$XDG_RUNTIME_DIR/eggpool.pid` → `~/.local/state/eggpool/eggpool.pid` → `/tmp/eggpool-<UID>.pid`, and is owned by the supervisor. If `eggpool serve` ever exits non-zero with a message about an existing instance, that is the duplicate-instance guard catching a live PID or a successful `/v1/healthz` probe — check `pgrep -f eggpool` before retrying.
|
|
@@ -5,9 +5,9 @@ port = 11300
|
|
|
5
5
|
log_level = "INFO"
|
|
6
6
|
access_log = true
|
|
7
7
|
# Granian runtime threads inside the single worker process. Keep workers=1.
|
|
8
|
-
# 1 is minimum footprint;
|
|
9
|
-
#
|
|
10
|
-
threads =
|
|
8
|
+
# 1 is minimum footprint; 4 is the default for installs that serve dashboard
|
|
9
|
+
# traffic during active proxy use. Granian still runs workers=1.
|
|
10
|
+
threads = 4
|
|
11
11
|
|
|
12
12
|
[upstream]
|
|
13
13
|
base_url = "https://opencode.ai/zen/go/v1"
|
|
@@ -1002,16 +1002,22 @@ async def _lifespan_runtime(app: FastAPI) -> AsyncGenerator[None]:
|
|
|
1002
1002
|
offset_30d_microdollars=acct_cfg.monthly_offset_microdollars,
|
|
1003
1003
|
)
|
|
1004
1004
|
|
|
1005
|
-
# 20. Metrics write coalescer for buffered analytics
|
|
1005
|
+
# 20. Metrics write coalescer for buffered analytics. Writes stay on the
|
|
1006
|
+
# primary connection; dashboard rollup reads use the same dedicated
|
|
1007
|
+
# read-only stats connection as the rest of StatsService when available.
|
|
1006
1008
|
rollup_repo = UsageRollupRepository(db)
|
|
1009
|
+
stats_rollup_repo = UsageRollupRepository(stats_db)
|
|
1010
|
+
stats_account_backoff_repo = (
|
|
1011
|
+
account_backoff_repo if stats_db is db else AccountBackoffRepository(stats_db)
|
|
1012
|
+
)
|
|
1007
1013
|
|
|
1008
1014
|
# 21. Statistics service
|
|
1009
1015
|
app.state.stats = StatsService(
|
|
1010
1016
|
stats_db,
|
|
1011
1017
|
health_manager=health_manager,
|
|
1012
1018
|
ping_repo=PingRepository(stats_db),
|
|
1013
|
-
account_backoff_repo=
|
|
1014
|
-
rollup_repo=
|
|
1019
|
+
account_backoff_repo=stats_account_backoff_repo,
|
|
1020
|
+
rollup_repo=stats_rollup_repo,
|
|
1015
1021
|
)
|
|
1016
1022
|
if config.dashboard.enabled and stats_db is db:
|
|
1017
1023
|
logger.warning(
|
|
@@ -11,7 +11,7 @@ import logging
|
|
|
11
11
|
import time
|
|
12
12
|
from dataclasses import dataclass
|
|
13
13
|
from datetime import UTC, datetime, timedelta
|
|
14
|
-
from typing import TYPE_CHECKING, Any, cast
|
|
14
|
+
from typing import TYPE_CHECKING, Any, TypeVar, cast
|
|
15
15
|
|
|
16
16
|
from fastapi import Request # noqa: TCH002 — FastAPI needs runtime access
|
|
17
17
|
from fastapi.responses import HTMLResponse, JSONResponse
|
|
@@ -45,7 +45,7 @@ from eggpool.stats.queries import fetch_disabled_account_count
|
|
|
45
45
|
from eggpool.stats.segmentation import serialize_canonical_request_segmentation
|
|
46
46
|
|
|
47
47
|
if TYPE_CHECKING:
|
|
48
|
-
from collections.abc import Iterable
|
|
48
|
+
from collections.abc import Awaitable, Iterable
|
|
49
49
|
|
|
50
50
|
from fastapi.responses import Response # noqa: TCH004
|
|
51
51
|
|
|
@@ -67,6 +67,26 @@ _RoutingPayload = tuple[
|
|
|
67
67
|
_PingsPayload = tuple[list[dict[str, Any]], list[dict[str, Any]]]
|
|
68
68
|
|
|
69
69
|
logger = logging.getLogger(__name__)
|
|
70
|
+
_DashboardStageResult = TypeVar("_DashboardStageResult")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
async def _await_dashboard_stage(
|
|
74
|
+
telemetry: Any | None,
|
|
75
|
+
page: str,
|
|
76
|
+
stage: str,
|
|
77
|
+
awaitable: Awaitable[_DashboardStageResult],
|
|
78
|
+
) -> _DashboardStageResult:
|
|
79
|
+
"""Await one dashboard operation and record its actual elapsed time."""
|
|
80
|
+
started = time.perf_counter()
|
|
81
|
+
try:
|
|
82
|
+
return await awaitable
|
|
83
|
+
finally:
|
|
84
|
+
if telemetry is not None:
|
|
85
|
+
telemetry.record_stage(
|
|
86
|
+
page,
|
|
87
|
+
stage,
|
|
88
|
+
(time.perf_counter() - started) * 1000,
|
|
89
|
+
)
|
|
70
90
|
|
|
71
91
|
|
|
72
92
|
@dataclass(frozen=True, slots=True)
|
|
@@ -504,6 +524,28 @@ def _normalize_group_by(group_by: str) -> str:
|
|
|
504
524
|
return group_by if group_by in _VALID_GROUP_BY else "provider_model"
|
|
505
525
|
|
|
506
526
|
|
|
527
|
+
def _aggregate_series_from_grouped(
|
|
528
|
+
grouped: dict[str, Any],
|
|
529
|
+
) -> list[dict[str, Any]]:
|
|
530
|
+
"""Reuse grouped bucket totals for the legacy aggregate table.
|
|
531
|
+
|
|
532
|
+
The dedicated timeseries page previously issued both a grouped query
|
|
533
|
+
and a second flat query, even though the grouped contract already
|
|
534
|
+
carries lossless ``bucket_totals``. Keeping the conversion here makes
|
|
535
|
+
the page's aggregate table a zero-cost view of the query it already
|
|
536
|
+
needs for the chart and detail table.
|
|
537
|
+
"""
|
|
538
|
+
totals = grouped.get("bucket_totals")
|
|
539
|
+
if not isinstance(totals, list):
|
|
540
|
+
return []
|
|
541
|
+
result: list[dict[str, Any]] = []
|
|
542
|
+
for row_obj in cast("list[object]", totals):
|
|
543
|
+
if isinstance(row_obj, dict):
|
|
544
|
+
row = cast("dict[str, Any]", row_obj)
|
|
545
|
+
result.append(dict(row))
|
|
546
|
+
return result
|
|
547
|
+
|
|
548
|
+
|
|
507
549
|
def _clamp_int(value: int, *, minimum: int, maximum: int) -> int:
|
|
508
550
|
"""Clamp an integer query value to an inclusive range."""
|
|
509
551
|
return max(minimum, min(value, maximum))
|
|
@@ -639,18 +681,22 @@ async def handle_overview(
|
|
|
639
681
|
time_range = resolve_time_range(period)
|
|
640
682
|
stats = request.app.state.stats
|
|
641
683
|
heatmap_range = _heatmap_time_range(dashboard_config.retain_request_stats_days)
|
|
684
|
+
telemetry = getattr(request.app.state, "dashboard_telemetry", None)
|
|
642
685
|
|
|
643
686
|
# Always fetch the disabled count so the Account breakdown empty
|
|
644
687
|
# state can offer a one-click opt-in even when no rows are
|
|
645
688
|
# currently visible. Cheap one-row aggregate; safe on every render.
|
|
646
|
-
disabled_count = await
|
|
689
|
+
disabled_count = await _await_dashboard_stage(
|
|
690
|
+
telemetry,
|
|
691
|
+
"overview",
|
|
692
|
+
"disabled_count",
|
|
693
|
+
_get_disabled_account_count(request, show_disabled),
|
|
694
|
+
)
|
|
647
695
|
|
|
648
696
|
# Fan out the independent stat reads concurrently. The single
|
|
649
697
|
# shared connection lock serializes per-query execution, so without
|
|
650
698
|
# this the page load is the sum of ten sequential round trips; with
|
|
651
699
|
# it the load is bounded by the slowest query instead.
|
|
652
|
-
telemetry = getattr(request.app.state, "dashboard_telemetry", None)
|
|
653
|
-
_gather_start = time.perf_counter()
|
|
654
700
|
(
|
|
655
701
|
accounts,
|
|
656
702
|
models,
|
|
@@ -665,70 +711,91 @@ async def handle_overview(
|
|
|
665
711
|
compression_runtime,
|
|
666
712
|
synthetic_cache_summary,
|
|
667
713
|
) = await asyncio.gather(
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
stats.get_model_stats(time_range, use_cache=True),
|
|
672
|
-
stats.get_recent_events(limit=10),
|
|
673
|
-
stats.get_bandwidth_timeseries(heatmap_range, use_cache=True),
|
|
674
|
-
stats.get_ping_summary(time_range, use_cache=True),
|
|
675
|
-
stats.get_ip_stats(time_range, use_cache=True),
|
|
676
|
-
stats.get_attempt_stats(time_range),
|
|
677
|
-
stats.get_operational_event_summary(time_range),
|
|
678
|
-
stats.get_pending_health_snapshot(),
|
|
679
|
-
stats.get_cache_observability(time_range.label, use_cache=True),
|
|
680
|
-
stats.get_compression_runtime(time_range.label, use_cache=True),
|
|
681
|
-
stats.get_synthetic_cache_summary(time_range.label, use_cache=True),
|
|
682
|
-
)
|
|
683
|
-
_gather_ms = (time.perf_counter() - _gather_start) * 1000
|
|
684
|
-
|
|
685
|
-
if telemetry is not None:
|
|
686
|
-
stage_names = [
|
|
687
|
-
"disabled_count",
|
|
714
|
+
_await_dashboard_stage(
|
|
715
|
+
telemetry,
|
|
716
|
+
"overview",
|
|
688
717
|
"account_stats",
|
|
718
|
+
stats.get_account_stats(
|
|
719
|
+
time_range, include_disabled=show_disabled, use_cache=True
|
|
720
|
+
),
|
|
721
|
+
),
|
|
722
|
+
_await_dashboard_stage(
|
|
723
|
+
telemetry,
|
|
724
|
+
"overview",
|
|
689
725
|
"model_stats",
|
|
726
|
+
stats.get_model_stats(time_range, use_cache=True),
|
|
727
|
+
),
|
|
728
|
+
_await_dashboard_stage(
|
|
729
|
+
telemetry,
|
|
730
|
+
"overview",
|
|
690
731
|
"recent_events",
|
|
732
|
+
stats.get_recent_events(limit=10),
|
|
733
|
+
),
|
|
734
|
+
_await_dashboard_stage(
|
|
735
|
+
telemetry,
|
|
736
|
+
"overview",
|
|
691
737
|
"bandwidth_daily",
|
|
738
|
+
stats.get_bandwidth_timeseries(heatmap_range, use_cache=True),
|
|
739
|
+
),
|
|
740
|
+
_await_dashboard_stage(
|
|
741
|
+
telemetry,
|
|
742
|
+
"overview",
|
|
692
743
|
"ping_summary",
|
|
744
|
+
stats.get_ping_summary(time_range, use_cache=True),
|
|
745
|
+
),
|
|
746
|
+
_await_dashboard_stage(
|
|
747
|
+
telemetry,
|
|
748
|
+
"overview",
|
|
693
749
|
"ip_stats",
|
|
750
|
+
stats.get_ip_stats(time_range, use_cache=True),
|
|
751
|
+
),
|
|
752
|
+
_await_dashboard_stage(
|
|
753
|
+
telemetry,
|
|
754
|
+
"overview",
|
|
694
755
|
"attempt_stats",
|
|
756
|
+
stats.get_attempt_stats(time_range, use_cache=True),
|
|
757
|
+
),
|
|
758
|
+
_await_dashboard_stage(
|
|
759
|
+
telemetry,
|
|
760
|
+
"overview",
|
|
695
761
|
"operational_summary",
|
|
762
|
+
stats.get_operational_event_summary(time_range, use_cache=True),
|
|
763
|
+
),
|
|
764
|
+
_await_dashboard_stage(
|
|
765
|
+
telemetry,
|
|
766
|
+
"overview",
|
|
696
767
|
"pending_health",
|
|
768
|
+
stats.get_pending_health_snapshot(use_cache=True),
|
|
769
|
+
),
|
|
770
|
+
_await_dashboard_stage(
|
|
771
|
+
telemetry,
|
|
772
|
+
"overview",
|
|
697
773
|
"cache_observability",
|
|
774
|
+
stats.get_cache_observability(time_range.label, use_cache=True),
|
|
775
|
+
),
|
|
776
|
+
_await_dashboard_stage(
|
|
777
|
+
telemetry,
|
|
778
|
+
"overview",
|
|
698
779
|
"compression_runtime",
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
attempt_stats,
|
|
709
|
-
operational_summary,
|
|
710
|
-
pending_health,
|
|
711
|
-
cache_observability,
|
|
712
|
-
compression_runtime,
|
|
713
|
-
]
|
|
714
|
-
for stage_name, _stage_val in zip(stage_names, gather_results, strict=True):
|
|
715
|
-
telemetry.record_stage(
|
|
716
|
-
"overview", stage_name, _gather_ms / len(stage_names)
|
|
717
|
-
)
|
|
718
|
-
telemetry.record_stage("overview", "synthetic_cache_summary", _gather_ms)
|
|
719
|
-
|
|
780
|
+
stats.get_compression_runtime(time_range.label, use_cache=True),
|
|
781
|
+
),
|
|
782
|
+
_await_dashboard_stage(
|
|
783
|
+
telemetry,
|
|
784
|
+
"overview",
|
|
785
|
+
"synthetic_cache_summary",
|
|
786
|
+
stats.get_synthetic_cache_summary(time_range.label, use_cache=True),
|
|
787
|
+
),
|
|
788
|
+
)
|
|
720
789
|
# ``get_dashboard_overview`` is derived from ``accounts`` and the
|
|
721
790
|
# per-period summary; both are cache hits after the gather above.
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
791
|
+
overview = await _await_dashboard_stage(
|
|
792
|
+
telemetry,
|
|
793
|
+
"overview",
|
|
794
|
+
"dashboard_overview",
|
|
795
|
+
stats.get_dashboard_overview(
|
|
796
|
+
time_range, account_stats=accounts, use_cache=True
|
|
797
|
+
),
|
|
725
798
|
)
|
|
726
|
-
if telemetry is not None:
|
|
727
|
-
telemetry.record_stage(
|
|
728
|
-
"overview",
|
|
729
|
-
"dashboard_overview",
|
|
730
|
-
(time.perf_counter() - _overview_start) * 1000,
|
|
731
|
-
)
|
|
732
799
|
|
|
733
800
|
from eggpool.metrics.thinking import get_counter
|
|
734
801
|
|
|
@@ -1873,39 +1940,28 @@ async def handle_timeseries(
|
|
|
1873
1940
|
bounded_limit = clamp_grouped_limit(limit)
|
|
1874
1941
|
stats = request.app.state.stats
|
|
1875
1942
|
telemetry = getattr(request.app.state, "dashboard_telemetry", None)
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
"
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
time_range,
|
|
1889
|
-
bucket=bucket,
|
|
1890
|
-
group_by=group_by,
|
|
1891
|
-
limit=bounded_limit,
|
|
1892
|
-
account_name=account or None,
|
|
1893
|
-
model_id=model or None,
|
|
1894
|
-
use_cache=True,
|
|
1895
|
-
),
|
|
1943
|
+
grouped = await _await_dashboard_stage(
|
|
1944
|
+
telemetry,
|
|
1945
|
+
"timeseries",
|
|
1946
|
+
"timeseries_grouped",
|
|
1947
|
+
stats.get_grouped_timeseries(
|
|
1948
|
+
time_range,
|
|
1949
|
+
bucket=bucket,
|
|
1950
|
+
group_by=group_by,
|
|
1951
|
+
limit=bounded_limit,
|
|
1952
|
+
account_name=account or None,
|
|
1953
|
+
model_id=model or None,
|
|
1954
|
+
use_cache=True,
|
|
1896
1955
|
),
|
|
1897
1956
|
)
|
|
1898
|
-
|
|
1899
|
-
if telemetry is not None:
|
|
1900
|
-
telemetry.record_stage("timeseries", "timeseries_flat", _gather_ms)
|
|
1901
|
-
telemetry.record_stage("timeseries", "timeseries_grouped", _gather_ms)
|
|
1957
|
+
series = _aggregate_series_from_grouped(grouped)
|
|
1902
1958
|
theme_css, _, current_theme, available = _get_theme_data(request, theme)
|
|
1903
1959
|
account_options = _collect_account_options(request)
|
|
1904
1960
|
model_options = _collect_model_options(request)
|
|
1905
1961
|
_render_start = time.perf_counter()
|
|
1906
1962
|
response = HTMLResponse(
|
|
1907
1963
|
content=render_timeseries(
|
|
1908
|
-
series
|
|
1964
|
+
series,
|
|
1909
1965
|
bucket=bucket,
|
|
1910
1966
|
period=time_range.label,
|
|
1911
1967
|
theme_css=theme_css,
|
|
@@ -1227,8 +1227,8 @@
|
|
|
1227
1227
|
};
|
|
1228
1228
|
|
|
1229
1229
|
if (document.readyState === "loading") {
|
|
1230
|
-
document.addEventListener("DOMContentLoaded", bootstrap);
|
|
1230
|
+
document.addEventListener("DOMContentLoaded", namespace.bootstrap);
|
|
1231
1231
|
} else {
|
|
1232
|
-
bootstrap();
|
|
1232
|
+
namespace.bootstrap();
|
|
1233
1233
|
}
|
|
1234
1234
|
})();
|