eggpool 0.4.0__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eggpool-0.4.2/.github/workflows/ci.yml +37 -0
- eggpool-0.4.2/.github/workflows/release.yml +35 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/AGENTS.md +11 -3
- {eggpool-0.4.0 → eggpool-0.4.2}/CHANGELOG.md +10 -1
- {eggpool-0.4.0 → eggpool-0.4.2}/PKG-INFO +41 -4
- {eggpool-0.4.0 → eggpool-0.4.2}/README.md +40 -3
- {eggpool-0.4.0 → eggpool-0.4.2}/architecture/README.md +260 -4
- {eggpool-0.4.0 → eggpool-0.4.2}/config.example.toml +84 -7
- {eggpool-0.4.0 → eggpool-0.4.2}/docs/transcoding.md +32 -21
- {eggpool-0.4.0 → eggpool-0.4.2}/pyproject.toml +1 -1
- {eggpool-0.4.0 → eggpool-0.4.2}/scripts/check_database.py +5 -1
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/_share/config.example.toml +12 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/api/backoff.py +43 -25
- eggpool-0.4.2/src/eggpool/api/model_info.py +360 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/api/models.py +30 -2
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/api/proxy_request.py +10 -2
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/app.py +141 -12
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/catalog/cache.py +152 -12
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/catalog/service.py +262 -13
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/cli_full.py +881 -395
- eggpool-0.4.2/src/eggpool/config_utils.py +158 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/render.py +430 -3
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/routes.py +107 -9
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/static/dashboard.css +75 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/connection.py +124 -44
- eggpool-0.4.2/src/eggpool/db/schema/0036_model_info.sql +76 -0
- eggpool-0.4.2/src/eggpool/db/schema/0037_model_info_failure_count.sql +7 -0
- eggpool-0.4.2/src/eggpool/db/schema/0038_model_info_phase5.sql +33 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/checksums.json +4 -1
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/errors.py +4 -0
- eggpool-0.4.2/src/eggpool/integrations/aider.py +31 -0
- eggpool-0.4.2/src/eggpool/integrations/cline.py +26 -0
- eggpool-0.4.2/src/eggpool/integrations/codex.py +60 -0
- eggpool-0.4.2/src/eggpool/integrations/common.py +454 -0
- eggpool-0.4.2/src/eggpool/integrations/continue_dev.py +42 -0
- eggpool-0.4.2/src/eggpool/integrations/goose.py +40 -0
- eggpool-0.4.2/src/eggpool/integrations/kilo.py +32 -0
- eggpool-0.4.2/src/eggpool/integrations/openhands.py +42 -0
- eggpool-0.4.2/src/eggpool/integrations/qwen_code.py +31 -0
- eggpool-0.4.2/src/eggpool/integrations/roo_code.py +26 -0
- eggpool-0.4.2/src/eggpool/model_info/__init__.py +1 -0
- eggpool-0.4.2/src/eggpool/model_info/dedup.py +43 -0
- eggpool-0.4.2/src/eggpool/model_info/identity.py +67 -0
- eggpool-0.4.2/src/eggpool/model_info/repository.py +474 -0
- eggpool-0.4.2/src/eggpool/model_info/scheduler.py +137 -0
- eggpool-0.4.2/src/eggpool/model_info/service.py +1301 -0
- eggpool-0.4.2/src/eggpool/model_info/sources/__init__.py +1 -0
- eggpool-0.4.2/src/eggpool/model_info/sources/artificial_analysis.py +280 -0
- eggpool-0.4.2/src/eggpool/model_info/sources/base.py +77 -0
- eggpool-0.4.2/src/eggpool/model_info/sources/huggingface.py +239 -0
- eggpool-0.4.2/src/eggpool/model_info/sources/openrouter.py +359 -0
- eggpool-0.4.2/src/eggpool/model_info/sources/provider_catalog.py +103 -0
- eggpool-0.4.2/src/eggpool/model_info/types.py +96 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/models/config.py +107 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/providers/connect.py +2 -26
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/request/coordinator.py +63 -5
- eggpool-0.4.2/src/eggpool/routing/fairness.py +143 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/routing/router.py +374 -20
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/transcoder/anthropic_to_openai.py +42 -52
- eggpool-0.4.2/src/eggpool/transcoder/json_helpers.py +45 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/transcoder/openai_to_anthropic.py +28 -29
- eggpool-0.4.2/src/eggpool/transcoder/policy.py +56 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/transcoder/usage.py +11 -7
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/update_checker.py +15 -5
- {eggpool-0.4.0 → eggpool-0.4.2}/uv.lock +1 -1
- eggpool-0.4.0/.github/workflows/ci.yml +0 -62
- eggpool-0.4.0/.github/workflows/release.yml +0 -31
- eggpool-0.4.0/src/eggpool/transcoder/policy.py +0 -40
- {eggpool-0.4.0 → eggpool-0.4.2}/.env.example +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/.gitignore +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/LICENSE +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/config-examples/claude-code.env +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/config-examples/opencode.jsonc +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/deploy/eggpool-logrotate.conf +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/deploy/eggpool.service +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/deploy/env.example +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/docs/backup-restore.md +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/docs/deployment.md +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/docs/filesystem-layout.md +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/docs/firewall.md +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/docs/model-limits.md +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/docs/network-diagnostics.md +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/docs/providers.md +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/docs/proxy.md +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/docs/raspberry-pi.md +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/scripts/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/scripts/install.sh +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/scripts/install_prompt.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/scripts/smoke_test.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/scripts/validate_routing.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/scripts/verify_upstream_auth.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/__main__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/_share/.env.example +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/accounts/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/accounts/registry.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/accounts/state.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/api/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/api/chat_completions.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/api/errors.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/api/messages.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/api/network.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/api/runtime.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/api/stats.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/api/update.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/auth.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/background/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/background/backup.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/background/cleanup.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/catalog/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/catalog/catalog_resolvers.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/catalog/fetcher.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/catalog/limits.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/catalog/normalizer.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/catalog/pricing.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/catalog/pricing_aliases.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/catalog/pricing_resolver.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/catalog/protocols.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/cli.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/config.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/constants.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/cost_recompute.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/_resources.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/escape.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/static/chart.umd.min.js +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/static/dashboard.js +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/static/favicon.svg +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/theme.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Booberry.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Catppuccin Latte.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Catppuccin Macchiato.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Catppuccin Mocha.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Cyber Red.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Cyberpunk.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Dark Green.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Discord (80_ Saturation).toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Discord.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Dracula.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Ferra Light.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Flexor Dark.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Gruvbox.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Halcyon Dark.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/IntelliJ Light.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Kanagawa.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Macaw Dark.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Macaw Light.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Matrix.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Noctis Lilac.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Nord.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Nostromo Terminal.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/One Dark.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Oxocarbon.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Rose Pine Dawn.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Rose Pine Moon.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Rose Pine.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Solarized Dark.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Sonokai.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Tokyo Night Storm.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/VESPER.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/Zenburn.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/acton.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/bam.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/base16-atelier-forest-light.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/berlin.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/black but with important highlights.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/broc.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/cork.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/ferra.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/forest.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/lisbon.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/midnight.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/oslo.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/plum.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/portland.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/sunset.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/tofino.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/vanimo.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/dashboard/themes/vik.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/migrations.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/repositories.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/rollup_repository.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0001_initial.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0002_indexes.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0003_request_attempts.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0004_integration_hardening.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0005_price_microdollars.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0006_correct_price_microdollars.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0007_price_cache_rates.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0008_proxy_request_identity.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0009_model_protocol_source.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0010_health_probe.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0011_model_resolution_status.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0012_drop_reservations_estimated_microdollars.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0013_request_attempts_account_id_index.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0014_bandwidth_tracking.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0015_multi_provider.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0016_requests_provider_id.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0017_price_snapshots_provider_id.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0018_provider_pings.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0019_client_ip.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0020_performance_indexes.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0021_provider_model_metadata.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0022_dashboard_indexes.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0023_deprecated_model_placeholder.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0024_account_backoffs.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0025_stale_request_index.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0026_attempt_observability.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0027_routing_decisions.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0028_operational_events.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0029_latency_phases.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0030_model_pricing_aliases.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0031_price_snapshot_provenance.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0032_usage_rollups.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0033_request_provider_local_cost.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0034_transcoding_daily.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/db/schema/0035_routing_decision_score_components.sql +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/deploy/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/deploy_user.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/fastcli.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/health/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/health/backoff.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/health/circuit_breaker.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/health/health_manager.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/integrations/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/integrations/opencode.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/lifecycle/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/lifecycle/backup.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/lifecycle/uninstall.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/logging.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/metrics/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/metrics/buffer.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/models/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/models/api.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/models/database.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/models/domain.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/onboard.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/providers/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/providers/_templates.toml +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/providers/auth.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/providers/client_pool.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/providers/contract.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/providers/dns_cache.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/providers/outbound.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/providers/pproxy_transport.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/proxy/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/proxy/client.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/proxy/cost_reporting.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/proxy/sse_observer.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/proxy/usage.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/py.typed +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/quota/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/quota/audit.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/quota/estimation.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/quota/reservation.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/quota/scorer.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/request/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/request/attempt_finalizer.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/request/body.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/request/finalizer.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/request/limits.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/retry/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/retry/classification.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/routing/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/routing/eligibility.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/routing/provider.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/runtime.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/runtime_dispatch.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/runtime_metrics.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/runtime_paths.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/security/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/security/redaction.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/stats/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/stats/grouped_timeseries.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/stats/queries.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/stats/service.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/toml_edit.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/transcoder/__init__.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/transcoder/context.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/transcoder/errors.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/transcoder/ids.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/transcoder/protocol.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/transcoder/static_headers.py +0 -0
- {eggpool-0.4.0 → eggpool-0.4.2}/src/eggpool/transcoder/streaming.py +0 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
env:
|
|
10
|
+
PYTHONHASHSEED: "0"
|
|
11
|
+
TZ: UTC
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
lint:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: astral-sh/setup-uv@v4
|
|
19
|
+
- run: uv sync --frozen --extra dev
|
|
20
|
+
- run: uv run ruff format --check src/ tests/ scripts/
|
|
21
|
+
- run: uv run ruff check src/ tests/ scripts/
|
|
22
|
+
|
|
23
|
+
typecheck:
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- uses: astral-sh/setup-uv@v4
|
|
28
|
+
- run: uv sync --frozen --extra dev
|
|
29
|
+
- run: uv run pyright src/ scripts/
|
|
30
|
+
|
|
31
|
+
test:
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
- uses: astral-sh/setup-uv@v4
|
|
36
|
+
- run: uv sync --frozen --extra dev
|
|
37
|
+
- run: uv run pytest
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: astral-sh/setup-uv@v4
|
|
17
|
+
- run: uv sync --frozen --extra dev
|
|
18
|
+
|
|
19
|
+
- name: Run pre-commit checks
|
|
20
|
+
env:
|
|
21
|
+
PYTHONHASHSEED: "0"
|
|
22
|
+
TZ: UTC
|
|
23
|
+
run: |
|
|
24
|
+
uv run ruff format --check src/ tests/ scripts/
|
|
25
|
+
uv run ruff check src/ tests/ scripts/
|
|
26
|
+
uv run pyright src/ scripts/
|
|
27
|
+
uv run pytest
|
|
28
|
+
|
|
29
|
+
- name: Build package
|
|
30
|
+
run: uv build
|
|
31
|
+
|
|
32
|
+
- name: Create GitHub Release
|
|
33
|
+
env:
|
|
34
|
+
GH_TOKEN: ${{ github.token }}
|
|
35
|
+
run: gh release create "$GITHUB_REF_NAME" --generate-notes dist/*
|
|
@@ -71,11 +71,14 @@ CI sets `PYTHONHASHSEED=0` and `TZ=UTC`; reproduce locally for deterministic res
|
|
|
71
71
|
- **Provider contracts**: `compose_provider_url()` is the single source of truth for upstream URLs. See `architecture/README.md` § Provider Contracts and § Provider Contract Rendering.
|
|
72
72
|
- **Protocol transcoding**: transparent request/response format conversion between OpenAI and Anthropic protocols. Phase 2 body translation, Phase 3 streaming SSE translation, Phase 4 routing eligibility widening, and Phase 5 operator controls and docs are implemented in `src/eggpool/transcoder/` and `src/eggpool/request/coordinator.py`. See `architecture/README.md` § Protocol Transcoding.
|
|
73
73
|
- **Database invariants**: SQLite WAL, single-connection serialization, `async with db.transaction():` for all DML. See `architecture/README.md` § Database.
|
|
74
|
-
- **Quota and routing**: tier-based routing via `routing_priority`, `QuotaFairScorer`, upstream-authoritative suppression. See `architecture/README.md` § Quota and Routing.
|
|
74
|
+
- **Quota and routing**: tier-based routing via `routing_priority`, `QuotaFairScorer`, upstream-authoritative suppression, same-tier fairness rotor. See `architecture/README.md` § Quota and Routing.
|
|
75
75
|
- **Error hierarchy**: `AggregatorError` → `UpstreamError` → specific subclasses. See `architecture/README.md` § Error Hierarchy.
|
|
76
76
|
- **Process model**: supervisor + Granian worker, PID file lifecycle, daemon mode. See `architecture/README.md` § Daemon Mode.
|
|
77
|
-
- **Dashboard**: server-rendered HTML,
|
|
77
|
+
- **Dashboard**: server-rendered HTML, 13 pages (including `/models/{model_id:path}` model-info detail), Chart.js v4, grouped timeseries, CSS tooltips. See `architecture` skill § Dashboard.
|
|
78
78
|
- **Observability**: attempt analytics, routing analytics, latency phases, pending health, runtime metrics. See `architecture` skill § Runtime Observability.
|
|
79
|
+
- **Catalog refresh semantics**: `CatalogService._fetch_and_process_account` returns an `AccountCatalogOutcome` enum (`SUCCESS_AUTHORITATIVE`, `SUCCESS_PARTIAL`, `SUCCESS_EMPTY`, `FAILED`, `SKIPPED`). Per-account cache updates run through `ModelCatalogCache.update_from_account(authoritative, allow_withdrawals)`, which is **non-destructive by default** — both flags default to `False`. The destructive `mark_account_models_unavailable` step is gated on `authoritative=True AND allow_withdrawals=True`, so the cache layer itself enforces the invariant. `ModelsConfig.catalog_withdrawal_policy` (`preserve_until_health` (default), `confirmed_once`, `confirmed_twice`) governs when the service flips `allow_withdrawals=True`. `SUCCESS_PARTIAL` always forces withdrawal off because a partial response is never a complete withdrawal confirmation. The per-cycle INFO summary at `_log_refresh_summary` reports per-outcome counts so operators can spot catalog uncertainty without enabling debug logging. See `architecture/README.md` § Catalog Refresh Semantics.
|
|
80
|
+
- **Agent integrations**: `eggpool configsetup` generates configuration snippets for 11 coding agents (OpenCode, Claude Code, Aider, Codex, Qwen Code, Kilo, Continue, Cline, Roo Code, Goose, OpenHands). Target-specific generators live in `src/eggpool/integrations/`; shared utilities in `src/eggpool/config_utils.py` (`resolve_server_api_key`, `ServerKeyResolution`). Transcoder enablement is persisted to TOML when required. Secret output is controlled explicitly via `contains_secret`. See `architecture/README.md` § Package Structure.
|
|
81
|
+
- **Model information**: `model_info/` sidecar subsystem with persistent metadata, provider-native observations (`ProviderCatalogSource`), OpenRouter metadata source (`OpenRouterModelInfoSource` — TTL-cached, exact alias matching, uses shared outbound client), identity resolution (`model_info/identity.py` — exact/curated alias matching only, no fuzzy matching), status classification, lifecycle wiring (`ModelInfoService`), background refresh scheduler (`ModelInfoRefreshScheduler`), catalog-refresh-driven reconciliation (`CatalogRefreshResult`), and CLI inspection. `ModelInfoSourceFetchError` for source failures. Phase 4 adds JSON API endpoints (`GET /api/model-info`, `GET /api/model-info/{model_id}`, `GET /api/model-info/sources`, `POST /api/model-info/refresh`), compact `/v1/models` enrichment under `eggpool.model_info`, and dashboard model-info status pills with tooltips. Phase 5 adds Artificial Analysis and Hugging Face source adapters, manual overrides (`[model_info.overrides]`), alias expansion (`[model_info.aliases]`), source health hardening (`rate_limited_until`, `last_status_code`, `last_payload_count`, `last_success_duration_ms`), richer summary generation (benchmarks, HF metadata, conflicts), and `model_info_overrides` table (migration `0037`). See `architecture/README.md` § Model Information.
|
|
79
82
|
|
|
80
83
|
## Gotchas
|
|
81
84
|
|
|
@@ -83,11 +86,15 @@ CI sets `PYTHONHASHSEED=0` and `TZ=UTC`; reproduce locally for deterministic res
|
|
|
83
86
|
- No pre-commit hooks are configured in this repo; CI runs ruff, pyright, and pytest via GitHub Actions
|
|
84
87
|
- **`static_models` is the source of truth for provider-specific protocol** — `FAMILY_PROTOCOLS` (`src/eggpool/catalog/protocols.py`) is a global fallback. Providers like `minimax-cn` that serve MiniMax models on the OpenAI-compatible surface **must** ship `[[providers.<id>.static_models]]` rows with `protocol = "openai"`, otherwise the live `/v1/models` fetch resolves `MiniMax-M*` via the `minimax-` family prefix to `anthropic` and the protocol check clears it to `None`, producing `ModelUnavailableError` instead of `ProtocolMismatchError`. Static seeds survive via `ModelCatalogCache._preserve_static_fields` (`src/eggpool/catalog/cache.py:146-187`).
|
|
85
88
|
- **Upstream-authoritative suppression**: local quota estimates are advisory by default (`local_quota_mode = "score_only"`). Only upstream-observed failures (429/402/5xx/auth) and explicit operator disablement suppress routing. Switch to `hard_cap` only as an opt-in escape hatch.
|
|
89
|
+
- **Catalog non-destructive refresh**: `ModelCatalogCache.update_from_account(authoritative=False, allow_withdrawals=False)` (the default) preserves prior account/model support on every cycle — failed, empty, and partial refreshes never de-pool a healthy account. The destructive `mark_account_models_unavailable` step is only reached when the service explicitly sets both flags, gated by `ModelsConfig.catalog_withdrawal_policy`. Under the default `preserve_until_health` policy the flags are never flipped, so `HealthManager` is the only de-pooling mechanism. Static seeds (`_seed_static_models`) inherit the non-destructive default and survive future refresh cycles via `_preserve_static_fields`.
|
|
90
|
+
- **Sibling-wins protocol guard**: `_provider_models` is keyed by `(model_id, provider_id)` and **shared** by every account that lists that provider (e.g. all `opencode-go-0001`/`-0002`/`-0003` accounts share one row per model on the `opencode-go` provider). `_preserve_resolved_protocol()` (`src/eggpool/catalog/cache.py`) is invoked on every non-destructive `update_from_account` call: when a per-provider row already has a resolved protocol and the new entry arrives with `protocol=None`, the prior protocol is preserved. This prevents a single sibling's partial refresh from silently de-pooling every account on that provider. The destructive path (`authoritative=True AND allow_withdrawals=True`) intentionally skips the guard.
|
|
91
|
+
- **Same-tier fairness rotor**: EggPool is not purely lowest-score-wins for same-tier peer accounts. When accounts are effectively tied by priority, weight, health, protocol, and utilization score, same-tier fairness rotates candidates to avoid stable config-order bias and subscription starvation. When `fairness_mode = "round_robin"` (the default), effectively tied accounts within the same priority tier are rotated deterministically via `FairnessRotor` (`src/eggpool/routing/fairness.py`). Band membership requires same priority, same weight, same transcode status, and score within `fairness_epsilon` of the best. The server runtime honors all `[routing]` fairness config fields: `fairness_mode`, `fairness_epsilon`, and `fairness_scope`. The `fairness_scope` controls rotation group granularity: `provider_model_protocol` (default) includes the routed protocol in the key, `provider_model` intentionally collapses protocol groups, and `priority_model_protocol` intentionally co-balances same-priority providers serving the same model. The rotor's position map is capped at 4096 entries (`_ROTOR_HARD_CAP`); when reached the entire map is cleared and rotation restarts from 0. Restart also resets all rotor state — durable round-robin is explicitly out of scope.
|
|
86
92
|
- **Backoff persistence**: upstream-derived backoffs survive restarts via the `account_backoffs` table (`src/eggpool/db/schema/0024_account_backoffs.sql`). Local cost overruns must never be persisted as backoff rows.
|
|
87
93
|
- **Synthetic 503 vs 502**: `ModelUnavailableError` (503) is reserved for genuine pre-dispatch unavailability. `UpstreamExhaustedError` (502) is raised when every candidate account was attempted and exhausted mid-request.
|
|
88
94
|
- **Streaming finalizer shielding**: streaming `_build_stream_generator` finalization runs under `asyncio.shield(asyncio.wait_for(..., timeout=10))` so ASGI task cancellation cannot kill the finalizer while it holds the DB lock. Leaks that escape this path are caught by the periodic `stale_request_finalizer` background task (`app._finalize_stale_requests`, runs every 60s).
|
|
89
95
|
- **Routing-decision score components** (`RoutingDecisionTrace.score_components`) carry the per-account score breakdown on every persisted `routing_decisions` row (`score_components_json`, migration `0035`). The dashboard and `eggpool accounts explain` consume this directly; do not re-score from quota tables when only the diagnostic breakdown is needed.
|
|
90
96
|
- **`_select_lock` publish ordering**: in `RequestCoordinator._select_and_persist_attempt()`, runtime publication (`Router.increment_active_request_count` + `QuotaEstimator.add_reservation`) must run INSIDE `_select_lock` AFTER the durable transaction commits but BEFORE the lock releases. The two contexts are explicit nested `async with` blocks (outer `_select_lock`, inner `_db.transaction()`). Key invariant: block placement (publication outside DB transaction body, inside `_select_lock`), not context-exit order.
|
|
97
|
+
- **`db.transaction()` nesting semantics** (`src/eggpool/db/connection.py`): nesting is detected via SQLite's per-connection `conn.in_transaction`, NOT task identity. A shielded or `create_task` child entering `db.transaction()` while the parent's `BEGIN IMMEDIATE` is still open piggybacks on the outer's commit boundary; the child never re-issues `BEGIN` and never acquires `_connection_lock` (the outer holds it). This fixes the AB/BA deadlock where a shielded child would block waiting for `_connection_lock` while the parent awaited `asyncio.shield()`. Two ContextVars back the semantics: `_in_transaction_context: ContextVar[bool]` is set/reset by both nested and outermost paths and is inherited by shielded/child tasks so they can `execute_write`; `_transaction_owner: ContextVar[Task]` is set only by the outermost path and is used by `vacuum()` to refuse running when the *current* task holds the lock (deadlock guard). `vacuum()` and `_require_transaction_owner()` consult `_in_transaction_context`, not `_transaction_owner`, so shielded/child task writes piggyback cleanly. Regression coverage lives in `tests/unit/test_database.py::TestTransactionNestingAcrossTaskBoundaries`.
|
|
91
98
|
- **`eggpool accounts explain` reads from SQLite, not an empty cache**: the command hydrates the catalog via `ModelCatalogCache.hydrate_from_db(db)` from `models`/`provider_model_metadata`/`account_models` rows. A thin `_CatalogShim` exposes the loaded cache as a `CatalogService`-compatible object. Output uses `click.echo` (no `rich` dependency).
|
|
92
99
|
- **Startup crash recovery**: `_crash_recovery` runs at every startup and recovers ALL pending requests and active reservations with no time threshold. A process restart is a definitive boundary.
|
|
93
100
|
- **Pricing pipeline**: prices flow TOML override → upstream metadata → external catalog (OpenRouter / OpenCode Zen via the alias registry). Cost precedence: `provider_reported > derived/partial/exact > estimated > unknown`.
|
|
@@ -96,6 +103,7 @@ CI sets `PYTHONHASHSEED=0` and `TZ=UTC`; reproduce locally for deterministic res
|
|
|
96
103
|
- **DNS cache**: `OutboundClientManager` and `ProviderClientPool` both integrate a `DnsNetworkBackend` that caches resolved DNS entries. Controlled by `[network.dns_cache]` (enabled by default, TTL 1800s, max 50 entries). Exposes precise counters and derived rates for operator diagnostics.
|
|
97
104
|
- **Memory footprint caps**: every growth axis is bounded by hardcoded caps (`EWMA_HARD_CAP = 4096`, `GLOBAL_EWMA_HARD_CAP = 1024`, `MAX_TRACKED_HOSTS = 256`). Regression gate: `tests/integration/test_memory.py` (`pytest.mark.slow`). See `plans/memory.md` for the full design.
|
|
98
105
|
- **Transcoder body translation**: `select_transcoder()` in `src/eggpool/transcoder/protocol.py` is the single source of truth for translator dispatch. Loss-of-information warnings are accumulated on `TranscodeContext.loss_warnings` and logged at request completion.
|
|
106
|
+
- **Agent config generation**: `eggpool configsetup` dispatches to target-specific generators in `src/eggpool/integrations/`. Shared utilities (model ID formatting, base URL defaults, clipboard/write logic) live in `src/eggpool/config_utils.py`. When adding a new target, create a new module in `integrations/` and register it in the Click command; reuse the shared utilities rather than reimplementing them.
|
|
99
107
|
|
|
100
108
|
## Error Handling
|
|
101
109
|
|
|
@@ -103,7 +111,7 @@ Use the hierarchy in `errors.py`. Chain exceptions with `raise ... from err` or
|
|
|
103
111
|
|
|
104
112
|
- `AggregatorError` → `ConfigError`, `DatabaseError`, `ProxyError`
|
|
105
113
|
- `UpstreamError` (has `status_code`) → `TemporaryUpstreamError`, `TransientUpstreamError`, `AuthenticationError`, `QuotaExhaustedError`, `RateLimitError` (has `retry_after`), `ModelUnavailableError`
|
|
106
|
-
- `ModelNotFoundError` (has `model_id`), `NoEligibleAccountError`, `CatalogUnavailableError`, `AuthenticationUnavailableError`, `UpstreamExhaustedError`, `AccountSuspendedError`, `RequestTooLargeError`, `ContextLimitExceededError`
|
|
114
|
+
- `ModelNotFoundError` (has `model_id`), `NoEligibleAccountError`, `CatalogUnavailableError`, `AuthenticationUnavailableError`, `UpstreamExhaustedError`, `AccountSuspendedError`, `RequestTooLargeError`, `ModelInfoSourceFetchError`, `ContextLimitExceededError`
|
|
107
115
|
|
|
108
116
|
## Fast-Path CLI
|
|
109
117
|
|
|
@@ -9,18 +9,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
+
- **Catalog non-destructive refresh contract.** `ModelCatalogCache.update_from_account()` now accepts `authoritative: bool = False, allow_withdrawals: bool = False`; both flags default to `False` so a failed, empty, or partial refresh cannot silently de-pool a healthy account. Operators opt into the old "refresh is the source of truth" behavior with `ModelsConfig.catalog_withdrawal_policy` (`preserve_until_health` default, `confirmed_once`, `confirmed_twice`). Under the default policy, health is the only de-pooling mechanism. `CatalogService._fetch_and_process_account()` returns an `AccountCatalogOutcome` enum (`SUCCESS_AUTHORITATIVE`, `SUCCESS_PARTIAL`, `SUCCESS_EMPTY`, `FAILED`, `SKIPPED`) plus an `AccountCatalogUpdateResult` summary row so the cache layer can stay non-destructive while operators still get full audit trail. `_log_refresh_summary` emits a single INFO line enumerating per-outcome counts after every cycle so catalog uncertainty shows up without enabling debug logging.
|
|
13
|
+
- **`eggpool accounts explain --gates`** (`src/eggpool/cli_full.py`) renders the per-account gate breakdown (config, credentials, health, circuit, provider id registry/catalog match, provider-supports-protocol, model support row/availability/freshness, provider-metadata-exists, protocol_match, local_quota_gate, final_eligible) as a compact text table. Backed by `Router.explain_account_eligibility(include_gates=True)` and `Router._collect_gate_status(...)`; the breakdown is informational — the canonical decision still comes from `_classify_eligibility`.
|
|
14
|
+
- **Same-tier account fairness rotor**: deterministic round-robin rotation for effectively tied same-tier accounts prevents routing starvation. Configurable via `[routing] fairness_mode`, `fairness_epsilon`, and `fairness_scope`. Defaults to `round_robin` with `provider_model_protocol` scope. Fairness decisions are recorded in `routing_decisions.score_components_json` under the `fairness` key for operator diagnostics. The `fairness` payload now includes the `scope` field, and each `top_candidates` entry carries `rank_before_fairness`, `rank_after_fairness`, and `fairness_band_member` for full traceability. The `FairnessRotor` position map is capped at 4096 entries (`_ROTOR_HARD_CAP`); when reached the entire map is cleared and rotation restarts from 0.
|
|
12
15
|
- **Bidirectional OpenAI ↔ Anthropic protocol transcoding.** When `[transcoder] enabled = true`, requests from clients using one protocol can be forwarded to upstream accounts that speak only the other. Initial scope is text-only requests and responses, plus streaming SSE. Tool calls, vision, and extended thinking land in a follow-up release. See `docs/transcoding.md` for the full translation table.
|
|
13
16
|
- New `eggpool stats transcoding [--period 1d|7d|30d]` subcommand for transcoding observability.
|
|
14
17
|
- New "Transcoding" card on the `/runtime` dashboard page.
|
|
15
18
|
- Structured INFO log per transcoded request with `request_id`, protocol direction, account, and loss-warning count.
|
|
16
19
|
- Boot-time INFO line when `[transcoder] enabled = true` so operators see the configuration at startup.
|
|
17
20
|
- **`routing_decisions.score_components_json` column (migration `0035`)** carries the per-account score breakdown captured by `QuotaFairScorer` at the moment the coordinator chose the selected account. The dashboard can now answer "why account A over account B?" without rescoring from quota tables. Includes `quota_score`, `inflight_penalty`, `health_penalty`, `final_score`, `weight`, `active_request_count`, `reserved_microdollars`, per-window `cost_*` and `capacity_*` microdollar values, `tier`, `requires_transcode`, and the top 5 near-tie candidates.
|
|
18
|
-
- **`eggpool accounts explain --model <id> [--provider P] [--protocol P]`** subcommand (`src/eggpool/cli_full.py`) renders a Rich table listing every registered account with its live eligibility verdict and a stable `reason_code` (`disabled`, `auth_failed`, `quota_exhausted`, `cooldown`, `rate_limited`, `circuit_open`, `wrong_provider`, `no_protocol`, `protocol_mismatch`, `no_model`, `model_stale`, `ok`). Re-evaluated on every invocation against the live registry + catalog so operators can diagnose routing skew without restarting the service.
|
|
21
|
+
- **`eggpool accounts explain --model <id> [--provider P] [--protocol P] [--scores]`** subcommand (`src/eggpool/cli_full.py`) renders a Rich table listing every registered account with its live eligibility verdict and a stable `reason_code` (`disabled`, `auth_failed`, `quota_exhausted`, `cooldown`, `rate_limited`, `circuit_open`, `wrong_provider`, `no_protocol`, `protocol_mismatch`, `no_model`, `model_stale`, `ok`). With `--scores`, eligible accounts are also scored and the output includes priority, weight, active request count, reserved microdollars, and routing score. Re-evaluated on every invocation against the live registry + catalog so operators can diagnose routing skew without restarting the service.
|
|
19
22
|
- **`GET /api/stats/routing/eligibility`** JSON counterpart (auth-gated via the existing stats-route dependency list, `src/eggpool/api/stats.py`) returns the same per-account verdict list as a JSON document for programmatic dashboards and alerting.
|
|
20
23
|
|
|
21
24
|
### Changed
|
|
22
25
|
|
|
23
26
|
- `RequestCoordinator` now carries `upstream_protocol` alongside `protocol` on `ProxyRequestContext`. Behaviour is identical when `[transcoder] enabled = false`.
|
|
27
|
+
- **`ModelCatalogCache.update_from_account()` semantic shift — now non-destructive by default.** Both `authoritative` and `allow_withdrawals` keyword arguments default to `False`, so a per-account catalog update that omits previously-known support rows no longer silently de-pools those rows. Updates that already pass `authoritative=True, allow_withdrawals=True` continue to behave as before (and are still the only way to remove support via a refresh). All in-tree callers that intentionally destroy support — the destructive `prune_unused()` cleanup step and the explicit `update_from_account(...)` calls in `tests/unit/test_catalog.py` / `tests/integration/test_catalog_unresolved_models.py` — now pass both flags explicitly so the test suite continues to assert legacy destructive behavior. See `architecture/README.md` § Catalog Refresh Semantics.
|
|
24
28
|
- **`RequestCoordinator._select_and_persist_attempt()` lock scope.** The runtime publication step (`Router.increment_active_request_count` + `QuotaEstimator.add_reservation`) now runs INSIDE `_select_lock` AFTER the durable transaction commits but BEFORE the lock releases. The two contexts are written as explicit nested `async with` blocks (outer `_select_lock`, inner `_db.transaction()`). Note: collapsing them back into the previous compound `async with self._select_lock, self._db.transaction():` form would NOT by itself re-introduce the stale-score race on context-exit-order grounds — Python exits context managers right-to-left, so the transaction would still commit before the lock released. The actual bug was that the runtime publication block lived INSIDE the transaction body, so active-count and reserved-cost state were published before the durable transaction committed. The explicit nested form makes it hard to accidentally place publication inside the transaction while still keeping publication under `_select_lock`. The key invariant is block placement (publication must be outside the DB transaction body but still inside `_select_lock`), not context-exit order. The compensation chain (decrement → finalize-as-cancelled → release health slot → set `client_metadata["post_commit_interrupted"]` → re-raise) is preserved and still catches `BaseException` (including `CancelledError` / `SystemExit` / `KeyboardInterrupt`, all re-raised without being swallowed).
|
|
25
29
|
- `RoutingScore` gains diagnostic fields (`reserved_microdollars`, `cost_5h_microdollars`, `cost_7d_microdollars`, `cost_30d_microdollars`, `capacity_5h_microdollars`, `capacity_7d_microdollars`, `capacity_30d_microdollars`, `active_request_count`) so the scorer can return enough state to populate `score_components_json` without a second pass over the quota tables.
|
|
26
30
|
- `RoutingDecisionTrace` gains `score_components: Mapping[str, Any] | None` plus `to_score_components_json()`; `RoutingDecisionRepository.create()` accepts an optional `score_components_json` argument (defaults to `'{}'` for backward compatibility with rows inserted by code paths that have not yet been migrated).
|
|
@@ -30,6 +34,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
30
34
|
- **`eggpool accounts status` now prints `routing_priority`.** The per-line output gained a `priority=N` field derived from the account's provider, alongside `provider`, `enabled`, `weight`, and the api-key-env set state.
|
|
31
35
|
- **`eggpool accounts explain` runs migrations on fresh installs.** The inner `_run_explain` coroutine now calls `MigrationRunner(db).run()` before hydrating `ModelCatalogCache.hydrate_from_db(db)`, so a brand-new (unmigrated) database path no longer crashes with `sqlite3.OperationalError: no such table: models` / `provider_model_metadata` / `account_models`. With no catalog rows yet, accounts surface a `no_model` verdict instead of the SQL error. The command still performs no outbound provider refresh.
|
|
32
36
|
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- **Cross-account protocol poisoning from a partial sibling refresh.** `_provider_models` is keyed by `(model_id, provider_id)` and shared by every account that lists that provider (e.g. all `opencode-go-0001`/`-0002`/`-0003` accounts share one row per model on the `opencode-go` provider). Prior to this fix a single sibling's partial refresh — transient upstream parse error, unresolved family prefix, or a model whose protocol cannot be re-derived this cycle — produced a model entry with `protocol=None` and clobbered the previously-resolved protocol on the shared row, silently dropping every sibling account from routing even though `_account_support` still listed them. `ModelCatalogCache._preserve_resolved_protocol()` now applies a sibling-wins guard: a non-destructive `update_from_account` will keep the existing resolved protocol when the new entry's `protocol` is `None` and the prior row carried a resolved value. The destructive path (`authoritative=True AND allow_withdrawals=True`) intentionally skips the guard so operator-initiated withdrawals remain effective. `tests/unit/test_catalog_withdrawal_policy.py::test_partial_refresh_does_not_clobber_shared_provider_protocol{,multiple}` and `test_explicit_destructive_update_can_still_clear_protocol` pin the new contract.
|
|
40
|
+
|
|
33
41
|
## [0.3.5] - 2026-06-27
|
|
34
42
|
|
|
35
43
|
### Changed
|
|
@@ -42,6 +50,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
42
50
|
|
|
43
51
|
- **Exclusion taxonomy empty-state**: the Routing page's doughnut chart now shows `<p class="empty">No exclusion data in this period.</p>` instead of an invisible Chart.js ring when no exclusions have been recorded in the selected period. The previous behaviour rendered a zero-data doughnut whose legend was visible but the chart itself was not, producing a "key but no graph" artefact.
|
|
44
52
|
- **`circuit_breaker` classification**: `SUPPRESSIVE_EXCLUSION_REASONS` now includes `circuit_breaker`, the only exclusion reason the coordinator actually writes to `exclude_reasons_json`. Previously every real-world exclusion landed in the `unknown` bucket because the frozenset only contained the legacy `circuit_open` name.
|
|
53
|
+
- **Catalog empty-data list is now classified `SUCCESS_EMPTY`, not `SUCCESS_AUTHORITATIVE`.** `CatalogService._fetch_and_process_account()` now distinguishes between "no model list in the response payload" (the existing `SUCCESS_EMPTY` branch on `result.response == {}`) and "model list returned but zero normalizable items after filter" (a new branch that fires when `normalize_models(...) == []` with `result.error is None`). Prior to this fix a fully empty but healthy upstream response was reported as authoritative and could mask a regression in the upstream `/v1/models` payload; the per-cycle summary line in operators' logs now correctly enumerates these as `empty=N`.
|
|
45
54
|
|
|
46
55
|
### Added
|
|
47
56
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: eggpool
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: A lightweight proxy that aggregates multiple LLM provider accounts behind one OpenAI-compatible endpoint
|
|
5
5
|
Project-URL: Homepage, https://github.com/eggstack/eggpool
|
|
6
6
|
Project-URL: Repository, https://github.com/eggstack/eggpool
|
|
@@ -59,6 +59,7 @@ A lightweight, LAN-hosted proxy that aggregates multiple AI provider accounts be
|
|
|
59
59
|
- Per-account outbound proxy support ([pproxy](https://pypi.org/project/pproxy/) — SOCKS5, HTTP, Shadowsocks)
|
|
60
60
|
- Tracks requests, tokens, latency, errors, and estimated costs in SQLite
|
|
61
61
|
- Multi-page dashboard with 50+ themes, reliability, routing, and runtime views
|
|
62
|
+
- Model metadata enrichment from provider catalogs, OpenRouter, Artificial Analysis, and Hugging Face
|
|
62
63
|
- Designed for lightweight deployments (Raspberry Pi, SBCs)
|
|
63
64
|
- Transparent protocol transcoding between OpenAI and Anthropic request formats
|
|
64
65
|
|
|
@@ -129,13 +130,16 @@ Use `eggpool connect` for interactive provider setup. See [docs/providers.md](do
|
|
|
129
130
|
| `[server]` | Bind address, port (default 11300), API key, logging, threads |
|
|
130
131
|
| `[upstream]` | Upstream API base URL, timeouts, connection pool |
|
|
131
132
|
| `[database]` | SQLite path, WAL mode |
|
|
132
|
-
| `[models]` | Catalog refresh, exposure mode, model collapse |
|
|
133
|
-
| `[routing]` | Routing strategy, retry limits, quota mode |
|
|
133
|
+
| `[models]` | Catalog refresh, exposure mode, model collapse, withdrawal policy |
|
|
134
|
+
| `[routing]` | Routing strategy, retry limits, quota mode, same-tier fairness |
|
|
134
135
|
| `[dashboard]` | Dashboard toggle, theme, refresh interval |
|
|
135
136
|
| `[providers.*]` | Provider configs with accounts and routing priority |
|
|
136
137
|
| `[network]` | Outbound transport, DNS cache |
|
|
138
|
+
| `[model_info]` | Optional model metadata refresh, aliases, overrides, and external source settings |
|
|
137
139
|
| `[transcoder]` | Protocol transcoding between OpenAI and Anthropic formats |
|
|
138
140
|
|
|
141
|
+
The catalog refresh is **non-destructive by default**: failed, empty, or partial upstream responses never silently de-pool a healthy account. Set `[models].catalog_withdrawal_policy` (`preserve_until_health` default, `confirmed_once`, `confirmed_twice`) to opt into destructive behavior on authoritative refreshes. See `architecture/README.md` § Catalog Refresh Semantics.
|
|
142
|
+
|
|
139
143
|
Full config reference: [`config.example.toml`](config.example.toml) | [docs/providers.md](docs/providers.md)
|
|
140
144
|
|
|
141
145
|
## Protocol transcoding
|
|
@@ -173,8 +177,12 @@ See [docs/transcoding.md](docs/transcoding.md) for the full translation table an
|
|
|
173
177
|
| `GET` | `/v1/healthz` | Liveness check |
|
|
174
178
|
| `GET` | `/v1/readyz` | Readiness check |
|
|
175
179
|
| `GET` | `/api/backoffs` | Active upstream-derived account backoffs (`?now=<epoch>` for reproducible snapshots) |
|
|
180
|
+
| `GET` | `/api/model-info` | Enriched model metadata summaries |
|
|
181
|
+
| `GET` | `/api/model-info/{model_id}` | Enriched metadata detail for one model |
|
|
182
|
+
| `GET` | `/api/model-info/sources` | Model-info source health |
|
|
183
|
+
| `POST` | `/api/model-info/refresh` | Trigger model-info refresh (auth-gated) |
|
|
176
184
|
|
|
177
|
-
When `[dashboard].enabled = true`, a multi-page dashboard is served at `/` with request stats, latency metrics, provider health, and more. Stats API available under `/api/stats/*`.
|
|
185
|
+
When `[dashboard].enabled = true`, a multi-page dashboard is served at `/` with request stats, latency metrics, provider health, model-info detail pages, and more. Stats API available under `/api/stats/*`.
|
|
178
186
|
|
|
179
187
|
## Documentation
|
|
180
188
|
|
|
@@ -201,6 +209,35 @@ uv run pyright src/ scripts/
|
|
|
201
209
|
uv run pytest
|
|
202
210
|
```
|
|
203
211
|
|
|
212
|
+
## Agent Configuration
|
|
213
|
+
|
|
214
|
+
`eggpool configsetup` generates configuration snippets for popular coding agents:
|
|
215
|
+
|
|
216
|
+
| Target | Command | Output | `--write` default | Model | Status |
|
|
217
|
+
|--------|---------|--------|-------------------|-------|--------|
|
|
218
|
+
| OpenCode | `eggpool configsetup opencode` | JSON provider config | N/A (clipboard) | auto | stable |
|
|
219
|
+
| Claude Code | `eggpool configsetup claude-code` | JSON snippet | N/A (clipboard) | N/A | stable |
|
|
220
|
+
| Aider | `eggpool configsetup aider` | Shell env exports | `.env.eggpool` | recommended | stable |
|
|
221
|
+
| Codex | `eggpool configsetup codex` | TOML provider block | N/A (printed) | recommended | version-sensitive |
|
|
222
|
+
| Qwen Code | `eggpool configsetup qwen-code` | JSON provider block | N/A (printed) | optional | verify schema |
|
|
223
|
+
| Kilo | `eggpool configsetup kilo` | JSON provider block | N/A (printed) | optional | verify schema |
|
|
224
|
+
| Continue | `eggpool configsetup continue` | YAML model block | `~/.continue/eggpool.yaml` | usually yes | stable fragment |
|
|
225
|
+
| Cline | `eggpool configsetup cline` | JSON profile | `cline-eggpool.json` | recommended | paste into UI |
|
|
226
|
+
| Roo Code | `eggpool configsetup roo-code` | JSON profile | `roo-code-eggpool.json` | recommended | paste into UI |
|
|
227
|
+
| Goose | `eggpool configsetup goose` | Shell env exports | N/A (printed) | recommended | verify env vars |
|
|
228
|
+
| OpenHands | `eggpool configsetup openhands` | Shell env exports | N/A (printed) | recommended | stable fragment |
|
|
229
|
+
|
|
230
|
+
Shared options: `--host`, `--base-url`, `--model`, `--write`, `--output`, `--force`, `--no-clipboard`, `--print-secret`.
|
|
231
|
+
Generated JSON, TOML, YAML, and shell snippets escape catalog/config values for
|
|
232
|
+
the target format, including provider-suffixed model IDs.
|
|
233
|
+
|
|
234
|
+
Examples:
|
|
235
|
+
```sh
|
|
236
|
+
eggpool configsetup aider --model openai/gpt-4 --write
|
|
237
|
+
eggpool configsetup continue --model claude-sonnet-4 --output ~/.continue/eggpool.yaml
|
|
238
|
+
eggpool configsetup cline --no-clipboard
|
|
239
|
+
```
|
|
240
|
+
|
|
204
241
|
## License
|
|
205
242
|
|
|
206
243
|
MIT
|
|
@@ -15,6 +15,7 @@ A lightweight, LAN-hosted proxy that aggregates multiple AI provider accounts be
|
|
|
15
15
|
- Per-account outbound proxy support ([pproxy](https://pypi.org/project/pproxy/) — SOCKS5, HTTP, Shadowsocks)
|
|
16
16
|
- Tracks requests, tokens, latency, errors, and estimated costs in SQLite
|
|
17
17
|
- Multi-page dashboard with 50+ themes, reliability, routing, and runtime views
|
|
18
|
+
- Model metadata enrichment from provider catalogs, OpenRouter, Artificial Analysis, and Hugging Face
|
|
18
19
|
- Designed for lightweight deployments (Raspberry Pi, SBCs)
|
|
19
20
|
- Transparent protocol transcoding between OpenAI and Anthropic request formats
|
|
20
21
|
|
|
@@ -85,13 +86,16 @@ Use `eggpool connect` for interactive provider setup. See [docs/providers.md](do
|
|
|
85
86
|
| `[server]` | Bind address, port (default 11300), API key, logging, threads |
|
|
86
87
|
| `[upstream]` | Upstream API base URL, timeouts, connection pool |
|
|
87
88
|
| `[database]` | SQLite path, WAL mode |
|
|
88
|
-
| `[models]` | Catalog refresh, exposure mode, model collapse |
|
|
89
|
-
| `[routing]` | Routing strategy, retry limits, quota mode |
|
|
89
|
+
| `[models]` | Catalog refresh, exposure mode, model collapse, withdrawal policy |
|
|
90
|
+
| `[routing]` | Routing strategy, retry limits, quota mode, same-tier fairness |
|
|
90
91
|
| `[dashboard]` | Dashboard toggle, theme, refresh interval |
|
|
91
92
|
| `[providers.*]` | Provider configs with accounts and routing priority |
|
|
92
93
|
| `[network]` | Outbound transport, DNS cache |
|
|
94
|
+
| `[model_info]` | Optional model metadata refresh, aliases, overrides, and external source settings |
|
|
93
95
|
| `[transcoder]` | Protocol transcoding between OpenAI and Anthropic formats |
|
|
94
96
|
|
|
97
|
+
The catalog refresh is **non-destructive by default**: failed, empty, or partial upstream responses never silently de-pool a healthy account. Set `[models].catalog_withdrawal_policy` (`preserve_until_health` default, `confirmed_once`, `confirmed_twice`) to opt into destructive behavior on authoritative refreshes. See `architecture/README.md` § Catalog Refresh Semantics.
|
|
98
|
+
|
|
95
99
|
Full config reference: [`config.example.toml`](config.example.toml) | [docs/providers.md](docs/providers.md)
|
|
96
100
|
|
|
97
101
|
## Protocol transcoding
|
|
@@ -129,8 +133,12 @@ See [docs/transcoding.md](docs/transcoding.md) for the full translation table an
|
|
|
129
133
|
| `GET` | `/v1/healthz` | Liveness check |
|
|
130
134
|
| `GET` | `/v1/readyz` | Readiness check |
|
|
131
135
|
| `GET` | `/api/backoffs` | Active upstream-derived account backoffs (`?now=<epoch>` for reproducible snapshots) |
|
|
136
|
+
| `GET` | `/api/model-info` | Enriched model metadata summaries |
|
|
137
|
+
| `GET` | `/api/model-info/{model_id}` | Enriched metadata detail for one model |
|
|
138
|
+
| `GET` | `/api/model-info/sources` | Model-info source health |
|
|
139
|
+
| `POST` | `/api/model-info/refresh` | Trigger model-info refresh (auth-gated) |
|
|
132
140
|
|
|
133
|
-
When `[dashboard].enabled = true`, a multi-page dashboard is served at `/` with request stats, latency metrics, provider health, and more. Stats API available under `/api/stats/*`.
|
|
141
|
+
When `[dashboard].enabled = true`, a multi-page dashboard is served at `/` with request stats, latency metrics, provider health, model-info detail pages, and more. Stats API available under `/api/stats/*`.
|
|
134
142
|
|
|
135
143
|
## Documentation
|
|
136
144
|
|
|
@@ -157,6 +165,35 @@ uv run pyright src/ scripts/
|
|
|
157
165
|
uv run pytest
|
|
158
166
|
```
|
|
159
167
|
|
|
168
|
+
## Agent Configuration
|
|
169
|
+
|
|
170
|
+
`eggpool configsetup` generates configuration snippets for popular coding agents:
|
|
171
|
+
|
|
172
|
+
| Target | Command | Output | `--write` default | Model | Status |
|
|
173
|
+
|--------|---------|--------|-------------------|-------|--------|
|
|
174
|
+
| OpenCode | `eggpool configsetup opencode` | JSON provider config | N/A (clipboard) | auto | stable |
|
|
175
|
+
| Claude Code | `eggpool configsetup claude-code` | JSON snippet | N/A (clipboard) | N/A | stable |
|
|
176
|
+
| Aider | `eggpool configsetup aider` | Shell env exports | `.env.eggpool` | recommended | stable |
|
|
177
|
+
| Codex | `eggpool configsetup codex` | TOML provider block | N/A (printed) | recommended | version-sensitive |
|
|
178
|
+
| Qwen Code | `eggpool configsetup qwen-code` | JSON provider block | N/A (printed) | optional | verify schema |
|
|
179
|
+
| Kilo | `eggpool configsetup kilo` | JSON provider block | N/A (printed) | optional | verify schema |
|
|
180
|
+
| Continue | `eggpool configsetup continue` | YAML model block | `~/.continue/eggpool.yaml` | usually yes | stable fragment |
|
|
181
|
+
| Cline | `eggpool configsetup cline` | JSON profile | `cline-eggpool.json` | recommended | paste into UI |
|
|
182
|
+
| Roo Code | `eggpool configsetup roo-code` | JSON profile | `roo-code-eggpool.json` | recommended | paste into UI |
|
|
183
|
+
| Goose | `eggpool configsetup goose` | Shell env exports | N/A (printed) | recommended | verify env vars |
|
|
184
|
+
| OpenHands | `eggpool configsetup openhands` | Shell env exports | N/A (printed) | recommended | stable fragment |
|
|
185
|
+
|
|
186
|
+
Shared options: `--host`, `--base-url`, `--model`, `--write`, `--output`, `--force`, `--no-clipboard`, `--print-secret`.
|
|
187
|
+
Generated JSON, TOML, YAML, and shell snippets escape catalog/config values for
|
|
188
|
+
the target format, including provider-suffixed model IDs.
|
|
189
|
+
|
|
190
|
+
Examples:
|
|
191
|
+
```sh
|
|
192
|
+
eggpool configsetup aider --model openai/gpt-4 --write
|
|
193
|
+
eggpool configsetup continue --model claude-sonnet-4 --output ~/.continue/eggpool.yaml
|
|
194
|
+
eggpool configsetup cline --no-clipboard
|
|
195
|
+
```
|
|
196
|
+
|
|
160
197
|
## License
|
|
161
198
|
|
|
162
199
|
MIT
|