eggpool 0.6.2__tar.gz → 0.6.4__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.4}/AGENTS.md +1 -1
- {eggpool-0.6.2 → eggpool-0.6.4}/PKG-INFO +1 -1
- {eggpool-0.6.2 → eggpool-0.6.4}/architecture/README.md +4 -4
- {eggpool-0.6.2 → eggpool-0.6.4}/config.example.toml +1 -3
- {eggpool-0.6.2 → eggpool-0.6.4}/docs/deployment.md +6 -6
- {eggpool-0.6.2 → eggpool-0.6.4}/docs/opencode-stream-stability.md +5 -5
- {eggpool-0.6.2 → eggpool-0.6.4}/docs/providers.md +6 -5
- {eggpool-0.6.2 → eggpool-0.6.4}/docs/raspberry-pi.md +6 -7
- {eggpool-0.6.2 → eggpool-0.6.4}/pyproject.toml +1 -1
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/_share/config.example.toml +3 -3
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/app.py +9 -3
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/render.py +44 -23
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/routes.py +156 -94
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/static/dashboard.js +104 -64
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/model_info/service.py +63 -20
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/model_info/sources/openrouter.py +130 -1
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/models/config.py +4 -4
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/stats/service.py +58 -5
- {eggpool-0.6.2 → eggpool-0.6.4}/uv.lock +1 -1
- {eggpool-0.6.2 → eggpool-0.6.4}/.env.example +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/.github/workflows/ci.yml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/.github/workflows/release.yml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/.gitignore +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/CHANGELOG.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/LICENSE +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/README.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/config-examples/claude-code.env +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/config-examples/opencode.jsonc +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/deploy/eggpool-logrotate.conf +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/deploy/eggpool.service +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/deploy/env.example +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/docs/backup-restore.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/docs/cache-compression-profiles.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/docs/cache-compression-troubleshooting.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/docs/cache-compression.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/docs/filesystem-layout.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/docs/firewall.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/docs/model-info-openrouter-debug.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/docs/model-limits.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/docs/network-diagnostics.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/docs/proxy.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/docs/thinking.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/docs/transcoding.md +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/scripts/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/scripts/check_database.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/scripts/debug_model_info_openrouter.sh +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/scripts/install.sh +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/scripts/install_prompt.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/scripts/repro_high_concurrency_streams.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/scripts/smoke_test.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/scripts/test_model_info_identity.sh +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/scripts/validate_routing.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/scripts/verify_upstream_auth.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/__main__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/_share/.env.example +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/accounts/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/accounts/registry.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/accounts/state.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/api/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/api/backoff.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/api/chat_completions.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/api/errors.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/api/messages.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/api/model_info.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/api/models.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/api/network.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/api/proxy_request.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/api/runtime.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/api/stats.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/api/update.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/auth.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/background/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/background/backup.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/background/cleanup.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/catalog/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/catalog/cache.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/catalog/capabilities.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/catalog/catalog_resolvers.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/catalog/fetcher.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/catalog/limits.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/catalog/models_dev.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/catalog/normalizer.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/catalog/pricing.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/catalog/pricing_aliases.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/catalog/pricing_resolver.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/catalog/protocols.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/catalog/service.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/cli.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/cli_full.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/config.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/config_utils.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/constants.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/cost_recompute.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/cost_repair.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/_resources.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/escape.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/static/chart.umd.min.js +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/static/dashboard.css +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/static/favicon.svg +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/telemetry.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/theme.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Booberry.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Catppuccin Latte.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Catppuccin Macchiato.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Catppuccin Mocha.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Cyber Red.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Cyberpunk.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Dark Green.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Discord (80_ Saturation).toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Discord.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Dracula.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Ferra Light.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Flexor Dark.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Gruvbox.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Halcyon Dark.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/IntelliJ Light.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Kanagawa.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Macaw Dark.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Macaw Light.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Matrix.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Noctis Lilac.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Nord.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Nostromo Terminal.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/One Dark.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Oxocarbon.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Rose Pine Dawn.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Rose Pine Moon.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Rose Pine.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Solarized Dark.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Sonokai.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Tokyo Night Storm.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/VESPER.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/Zenburn.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/acton.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/bam.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/base16-atelier-forest-light.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/berlin.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/black but with important highlights.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/broc.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/cork.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/ferra.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/forest.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/lisbon.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/midnight.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/oslo.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/plum.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/portland.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/sunset.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/tofino.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/vanimo.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/dashboard/themes/vik.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/connection.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/migrations.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/repositories.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/rollup_repository.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0001_initial.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0002_indexes.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0003_request_attempts.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0004_integration_hardening.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0005_price_microdollars.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0006_correct_price_microdollars.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0007_price_cache_rates.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0008_proxy_request_identity.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0009_model_protocol_source.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0010_health_probe.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0011_model_resolution_status.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0012_drop_reservations_estimated_microdollars.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0013_request_attempts_account_id_index.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0014_bandwidth_tracking.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0015_multi_provider.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0016_requests_provider_id.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0017_price_snapshots_provider_id.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0018_provider_pings.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0019_client_ip.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0020_performance_indexes.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0021_provider_model_metadata.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0022_dashboard_indexes.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0023_deprecated_model_placeholder.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0024_account_backoffs.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0025_stale_request_index.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0026_attempt_observability.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0027_routing_decisions.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0028_operational_events.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0029_latency_phases.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0030_model_pricing_aliases.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0031_price_snapshot_provenance.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0032_usage_rollups.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0033_request_provider_local_cost.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0034_transcoding_daily.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0035_routing_decision_score_components.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0036_model_info.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0037_model_info_failure_count.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0038_model_info_phase5.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0039_thinking_observability.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0040_cache_token_observability.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0041_canonical_request_segmentation.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0042_observe_mode_compression_accounting.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0043_safe_suffix_compression.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0044_compression_policy_resolver.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0045_synthetic_cache_controls.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0046_closed_loop_threshold_tuning.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0047_normalize_rollup_bucket_start.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0048_request_cost_repairs.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/0049_model_info_match_evidence.sql +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/db/schema/checksums.json +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/deploy/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/deploy_user.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/errors.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/fastcli.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/health/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/health/backoff.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/health/circuit_breaker.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/health/health_manager.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/integrations/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/integrations/aider.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/integrations/cline.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/integrations/codex.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/integrations/common.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/integrations/continue_dev.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/integrations/goose.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/integrations/kilo.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/integrations/opencode.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/integrations/openhands.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/integrations/qwen_code.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/integrations/roo_code.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/jsonx.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/lifecycle/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/lifecycle/backup.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/lifecycle/uninstall.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/logging.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/metrics/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/metrics/buffer.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/metrics/thinking.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/model_info/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/model_info/dedup.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/model_info/identity.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/model_info/matching.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/model_info/normalization.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/model_info/presentation.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/model_info/repository.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/model_info/scheduler.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/model_info/sources/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/model_info/sources/artificial_analysis.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/model_info/sources/base.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/model_info/sources/huggingface.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/model_info/sources/provider_catalog.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/model_info/types.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/models/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/models/api.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/models/database.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/models/domain.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/onboard.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/providers/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/providers/_templates.toml +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/providers/auth.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/providers/client_pool.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/providers/connect.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/providers/contract.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/providers/dns_cache.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/providers/outbound.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/providers/pproxy_transport.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/proxy/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/proxy/client.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/proxy/cost_reporting.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/proxy/normalized_usage.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/proxy/sse_observer.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/proxy/usage.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/py.typed +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/quota/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/quota/audit.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/quota/estimation.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/quota/reservation.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/quota/scorer.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/request/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/request/attempt_finalizer.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/request/body.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/request/coordinator.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/request/finalization_queue.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/request/finalizer.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/request/limits.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/request/routing_trace_guard.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/request/stream_diagnostics.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/retry/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/retry/classification.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/routing/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/routing/config.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/routing/eligibility.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/routing/fairness.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/routing/provider.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/routing/router.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/runtime.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/runtime_dispatch.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/runtime_metrics.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/runtime_paths.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/security/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/security/redaction.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/stats/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/stats/cache_metrics.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/stats/dashboard_explain.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/stats/grouped_timeseries.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/stats/queries.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/stats/segmentation.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/stats/transcoding.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/toml_edit.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/anthropic_to_openai.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/budget_resolver.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/cache_stability.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/cache_synthesis.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/cache_synthesis_policy.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/compression/__init__.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/compression/analyzer.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/compression/apply.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/compression/markers.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/compression/policy.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/compression/policy_resolver.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/compression/tuning.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/context.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/errors.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/ids.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/json_helpers.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/openai_to_anthropic.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/policy.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/prepared.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/protocol.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/segmentation.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/segmentation_guard.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/static_headers.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/streaming.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/transcoder/usage.py +0 -0
- {eggpool-0.6.2 → eggpool-0.6.4}/src/eggpool/update_checker.py +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.4
|
|
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(
|
|
@@ -362,6 +362,16 @@ def _format_tooltip_date(day_str: str) -> str:
|
|
|
362
362
|
return parsed.strftime("%a, %b %-d %Y")
|
|
363
363
|
|
|
364
364
|
|
|
365
|
+
def _heatmap_today() -> date:
|
|
366
|
+
"""Return the heatmap's UTC calendar day.
|
|
367
|
+
|
|
368
|
+
Request timestamps and the heatmap query range are normalized to UTC;
|
|
369
|
+
using the host-local date here would shift the final column around local
|
|
370
|
+
midnight on deployments whose system timezone is not UTC.
|
|
371
|
+
"""
|
|
372
|
+
return datetime.now(UTC).date()
|
|
373
|
+
|
|
374
|
+
|
|
365
375
|
def get_theme_css(theme_name: str, themes_dir: str | None = None) -> str:
|
|
366
376
|
"""Load a theme by name and return the CSS :root block, or empty string.
|
|
367
377
|
|
|
@@ -548,8 +558,8 @@ def _render_layout(
|
|
|
548
558
|
· <span id="dashboard-updated">ready</span>{update_indicator}</small>
|
|
549
559
|
</footer>
|
|
550
560
|
{script_block}
|
|
551
|
-
{chart_script}
|
|
552
561
|
{dashboard_script}
|
|
562
|
+
{chart_script}
|
|
553
563
|
</body>
|
|
554
564
|
</html>"""
|
|
555
565
|
|
|
@@ -1549,34 +1559,38 @@ def _render_bandwidth_heatmap(
|
|
|
1549
1559
|
day_requests: dict[str, int] = {}
|
|
1550
1560
|
if value_field == "bytes":
|
|
1551
1561
|
day_values: dict[str, int] = {}
|
|
1562
|
+
in_out: dict[str, tuple[int, int]] = {}
|
|
1552
1563
|
for row in daily_data:
|
|
1553
1564
|
day_str = str(row.get("day", ""))
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
str(row.get("day", "")): (
|
|
1560
|
-
int(row.get("bytes_received", 0)),
|
|
1561
|
-
int(row.get("bytes_emitted", 0)),
|
|
1565
|
+
received = int(row.get("bytes_received", 0))
|
|
1566
|
+
emitted = int(row.get("bytes_emitted", 0))
|
|
1567
|
+
day_values[day_str] = day_values.get(day_str, 0) + received + emitted
|
|
1568
|
+
day_requests[day_str] = day_requests.get(day_str, 0) + int(
|
|
1569
|
+
row.get("request_count", 0)
|
|
1562
1570
|
)
|
|
1563
|
-
|
|
1564
|
-
|
|
1571
|
+
previous_received, previous_emitted = in_out.get(day_str, (0, 0))
|
|
1572
|
+
in_out[day_str] = (
|
|
1573
|
+
previous_received + received,
|
|
1574
|
+
previous_emitted + emitted,
|
|
1575
|
+
)
|
|
1576
|
+
formatter: Any = format_bytes
|
|
1565
1577
|
elif value_field == "total_tokens":
|
|
1566
|
-
day_values = {
|
|
1567
|
-
str(row.get("day", "")): int(row.get("total_tokens", 0))
|
|
1568
|
-
for row in daily_data
|
|
1569
|
-
}
|
|
1578
|
+
day_values = {}
|
|
1570
1579
|
formatter = format_tokens
|
|
1571
1580
|
in_out = {}
|
|
1572
1581
|
for row in daily_data:
|
|
1573
1582
|
day_str = str(row.get("day", ""))
|
|
1574
|
-
|
|
1583
|
+
day_values[day_str] = day_values.get(day_str, 0) + int(
|
|
1584
|
+
row.get("total_tokens", 0)
|
|
1585
|
+
)
|
|
1586
|
+
day_requests[day_str] = day_requests.get(day_str, 0) + int(
|
|
1587
|
+
row.get("request_count", 0)
|
|
1588
|
+
)
|
|
1575
1589
|
else:
|
|
1576
1590
|
raise ValueError(f"unsupported heatmap value_field: {value_field!r}")
|
|
1577
1591
|
|
|
1578
1592
|
# Date range: last 180 days ending today
|
|
1579
|
-
today =
|
|
1593
|
+
today = _heatmap_today()
|
|
1580
1594
|
start_date = today - timedelta(days=179)
|
|
1581
1595
|
|
|
1582
1596
|
# Pad start to Sunday (GitHub convention: weeks start on Sunday)
|
|
@@ -1587,10 +1601,15 @@ def _render_bandwidth_heatmap(
|
|
|
1587
1601
|
# Calculate number of weeks
|
|
1588
1602
|
num_weeks = ((today - grid_start).days // 7) + 1
|
|
1589
1603
|
|
|
1590
|
-
# Find max value for color scaling
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1604
|
+
# Find max value for color scaling from the rendered window only. The
|
|
1605
|
+
# service may return a boundary row just outside the calendar window
|
|
1606
|
+
# because its query uses a rolling timestamp range; that row must not
|
|
1607
|
+
# make every visible day look artificially faint.
|
|
1608
|
+
visible_values = [
|
|
1609
|
+
day_values.get((start_date + timedelta(days=offset)).isoformat(), 0)
|
|
1610
|
+
for offset in range((today - start_date).days + 1)
|
|
1611
|
+
]
|
|
1612
|
+
max_val = max(visible_values, default=0) or 1
|
|
1594
1613
|
|
|
1595
1614
|
# Color scale (theme-aware or default GitHub Primer green)
|
|
1596
1615
|
colors = heatmap_colors or _DEFAULT_HEATMAP_COLORS
|
|
@@ -1671,7 +1690,6 @@ def _render_bandwidth_heatmap(
|
|
|
1671
1690
|
color = _get_color(value)
|
|
1672
1691
|
x = left_margin + week * step
|
|
1673
1692
|
y = top_margin + day_of_week * step
|
|
1674
|
-
tooltip = f"{day_str}: {formatter(value)}"
|
|
1675
1693
|
request_count = day_requests.get(day_str, 0)
|
|
1676
1694
|
request_count_text = (
|
|
1677
1695
|
f"{request_count:,} request{'s' if request_count != 1 else ''}"
|
|
@@ -1691,11 +1709,14 @@ def _render_bandwidth_heatmap(
|
|
|
1691
1709
|
f"{request_count_text}"
|
|
1692
1710
|
)
|
|
1693
1711
|
tooltip_attr = _stdlib_escape(tooltip_text, quote=True)
|
|
1712
|
+
tooltip_title_text = tooltip_text.replace("\n", " — ")
|
|
1713
|
+
tooltip_title = f"{day_str}: {tooltip_title_text}"
|
|
1694
1714
|
cells.append(
|
|
1695
1715
|
f'<rect x="{x}" y="{y}" width="{cell_size}" '
|
|
1696
1716
|
f'height="{cell_size}" rx="2" fill="{color}" '
|
|
1697
1717
|
f'class="heatmap-cell" pointer-events="none">'
|
|
1698
|
-
f"<title>{_stdlib_escape(
|
|
1718
|
+
f"<title>{_stdlib_escape(tooltip_title)}"
|
|
1719
|
+
f"</title></rect>"
|
|
1699
1720
|
)
|
|
1700
1721
|
hitboxes.append(
|
|
1701
1722
|
f'<div class="heatmap-hitbox" '
|