eggpool 0.3.7__tar.gz → 0.3.9__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.
Files changed (252) hide show
  1. {eggpool-0.3.7 → eggpool-0.3.9}/PKG-INFO +1 -1
  2. {eggpool-0.3.7 → eggpool-0.3.9}/pyproject.toml +1 -1
  3. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/catalog/catalog_resolvers.py +119 -24
  4. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/cli_full.py +17 -5
  5. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/providers/connect.py +27 -1
  6. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/transcoder/openai_to_anthropic.py +3 -1
  7. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/transcoder/streaming.py +19 -4
  8. {eggpool-0.3.7 → eggpool-0.3.9}/uv.lock +1 -1
  9. {eggpool-0.3.7 → eggpool-0.3.9}/.env.example +0 -0
  10. {eggpool-0.3.7 → eggpool-0.3.9}/.github/workflows/ci.yml +0 -0
  11. {eggpool-0.3.7 → eggpool-0.3.9}/.github/workflows/release.yml +0 -0
  12. {eggpool-0.3.7 → eggpool-0.3.9}/.gitignore +0 -0
  13. {eggpool-0.3.7 → eggpool-0.3.9}/AGENTS.md +0 -0
  14. {eggpool-0.3.7 → eggpool-0.3.9}/CHANGELOG.md +0 -0
  15. {eggpool-0.3.7 → eggpool-0.3.9}/LICENSE +0 -0
  16. {eggpool-0.3.7 → eggpool-0.3.9}/README.md +0 -0
  17. {eggpool-0.3.7 → eggpool-0.3.9}/architecture/README.md +0 -0
  18. {eggpool-0.3.7 → eggpool-0.3.9}/config-examples/claude-code.env +0 -0
  19. {eggpool-0.3.7 → eggpool-0.3.9}/config-examples/opencode.jsonc +0 -0
  20. {eggpool-0.3.7 → eggpool-0.3.9}/config.example.toml +0 -0
  21. {eggpool-0.3.7 → eggpool-0.3.9}/deploy/eggpool-logrotate.conf +0 -0
  22. {eggpool-0.3.7 → eggpool-0.3.9}/deploy/eggpool.service +0 -0
  23. {eggpool-0.3.7 → eggpool-0.3.9}/deploy/env.example +0 -0
  24. {eggpool-0.3.7 → eggpool-0.3.9}/docs/backup-restore.md +0 -0
  25. {eggpool-0.3.7 → eggpool-0.3.9}/docs/deployment.md +0 -0
  26. {eggpool-0.3.7 → eggpool-0.3.9}/docs/filesystem-layout.md +0 -0
  27. {eggpool-0.3.7 → eggpool-0.3.9}/docs/firewall.md +0 -0
  28. {eggpool-0.3.7 → eggpool-0.3.9}/docs/model-limits.md +0 -0
  29. {eggpool-0.3.7 → eggpool-0.3.9}/docs/network-diagnostics.md +0 -0
  30. {eggpool-0.3.7 → eggpool-0.3.9}/docs/providers.md +0 -0
  31. {eggpool-0.3.7 → eggpool-0.3.9}/docs/proxy.md +0 -0
  32. {eggpool-0.3.7 → eggpool-0.3.9}/docs/raspberry-pi.md +0 -0
  33. {eggpool-0.3.7 → eggpool-0.3.9}/docs/transcoding.md +0 -0
  34. {eggpool-0.3.7 → eggpool-0.3.9}/scripts/__init__.py +0 -0
  35. {eggpool-0.3.7 → eggpool-0.3.9}/scripts/check_database.py +0 -0
  36. {eggpool-0.3.7 → eggpool-0.3.9}/scripts/install.sh +0 -0
  37. {eggpool-0.3.7 → eggpool-0.3.9}/scripts/install_prompt.py +0 -0
  38. {eggpool-0.3.7 → eggpool-0.3.9}/scripts/smoke_test.py +0 -0
  39. {eggpool-0.3.7 → eggpool-0.3.9}/scripts/validate_routing.py +0 -0
  40. {eggpool-0.3.7 → eggpool-0.3.9}/scripts/verify_upstream_auth.py +0 -0
  41. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/__init__.py +0 -0
  42. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/__main__.py +0 -0
  43. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/_share/.env.example +0 -0
  44. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/_share/config.example.toml +0 -0
  45. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/accounts/__init__.py +0 -0
  46. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/accounts/registry.py +0 -0
  47. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/accounts/state.py +0 -0
  48. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/api/__init__.py +0 -0
  49. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/api/backoff.py +0 -0
  50. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/api/chat_completions.py +0 -0
  51. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/api/errors.py +0 -0
  52. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/api/messages.py +0 -0
  53. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/api/models.py +0 -0
  54. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/api/network.py +0 -0
  55. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/api/proxy_request.py +0 -0
  56. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/api/runtime.py +0 -0
  57. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/api/stats.py +0 -0
  58. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/api/update.py +0 -0
  59. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/app.py +0 -0
  60. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/auth.py +0 -0
  61. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/background/__init__.py +0 -0
  62. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/background/backup.py +0 -0
  63. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/background/cleanup.py +0 -0
  64. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/catalog/__init__.py +0 -0
  65. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/catalog/cache.py +0 -0
  66. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/catalog/fetcher.py +0 -0
  67. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/catalog/limits.py +0 -0
  68. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/catalog/normalizer.py +0 -0
  69. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/catalog/pricing.py +0 -0
  70. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/catalog/pricing_aliases.py +0 -0
  71. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/catalog/pricing_resolver.py +0 -0
  72. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/catalog/protocols.py +0 -0
  73. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/catalog/service.py +0 -0
  74. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/cli.py +0 -0
  75. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/config.py +0 -0
  76. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/constants.py +0 -0
  77. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/cost_recompute.py +0 -0
  78. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/__init__.py +0 -0
  79. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/_resources.py +0 -0
  80. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/escape.py +0 -0
  81. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/render.py +0 -0
  82. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/routes.py +0 -0
  83. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/static/chart.umd.min.js +0 -0
  84. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/static/dashboard.css +0 -0
  85. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/static/dashboard.js +0 -0
  86. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/static/favicon.svg +0 -0
  87. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/theme.py +0 -0
  88. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Booberry.toml +0 -0
  89. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Catppuccin Latte.toml +0 -0
  90. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Catppuccin Macchiato.toml +0 -0
  91. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Catppuccin Mocha.toml +0 -0
  92. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Cyber Red.toml +0 -0
  93. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Cyberpunk.toml +0 -0
  94. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Dark Green.toml +0 -0
  95. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Discord (80_ Saturation).toml +0 -0
  96. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Discord.toml +0 -0
  97. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Dracula.toml +0 -0
  98. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Ferra Light.toml +0 -0
  99. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Flexor Dark.toml +0 -0
  100. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Gruvbox.toml +0 -0
  101. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Halcyon Dark.toml +0 -0
  102. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/IntelliJ Light.toml +0 -0
  103. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Kanagawa.toml +0 -0
  104. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Macaw Dark.toml +0 -0
  105. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Macaw Light.toml +0 -0
  106. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Matrix.toml +0 -0
  107. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Noctis Lilac.toml +0 -0
  108. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Nord.toml +0 -0
  109. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Nostromo Terminal.toml +0 -0
  110. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/One Dark.toml +0 -0
  111. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Oxocarbon.toml +0 -0
  112. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Rose Pine Dawn.toml +0 -0
  113. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Rose Pine Moon.toml +0 -0
  114. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Rose Pine.toml +0 -0
  115. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Solarized Dark.toml +0 -0
  116. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Sonokai.toml +0 -0
  117. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Tokyo Night Storm.toml +0 -0
  118. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/VESPER.toml +0 -0
  119. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/Zenburn.toml +0 -0
  120. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/acton.toml +0 -0
  121. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/bam.toml +0 -0
  122. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/base16-atelier-forest-light.toml +0 -0
  123. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/berlin.toml +0 -0
  124. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/black but with important highlights.toml +0 -0
  125. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/broc.toml +0 -0
  126. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/cork.toml +0 -0
  127. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/ferra.toml +0 -0
  128. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/forest.toml +0 -0
  129. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/lisbon.toml +0 -0
  130. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/midnight.toml +0 -0
  131. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/oslo.toml +0 -0
  132. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/plum.toml +0 -0
  133. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/portland.toml +0 -0
  134. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/sunset.toml +0 -0
  135. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/tofino.toml +0 -0
  136. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/vanimo.toml +0 -0
  137. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/dashboard/themes/vik.toml +0 -0
  138. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/__init__.py +0 -0
  139. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/connection.py +0 -0
  140. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/migrations.py +0 -0
  141. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/repositories.py +0 -0
  142. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/rollup_repository.py +0 -0
  143. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0001_initial.sql +0 -0
  144. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0002_indexes.sql +0 -0
  145. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0003_request_attempts.sql +0 -0
  146. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0004_integration_hardening.sql +0 -0
  147. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0005_price_microdollars.sql +0 -0
  148. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0006_correct_price_microdollars.sql +0 -0
  149. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0007_price_cache_rates.sql +0 -0
  150. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0008_proxy_request_identity.sql +0 -0
  151. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0009_model_protocol_source.sql +0 -0
  152. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0010_health_probe.sql +0 -0
  153. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0011_model_resolution_status.sql +0 -0
  154. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0012_drop_reservations_estimated_microdollars.sql +0 -0
  155. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0013_request_attempts_account_id_index.sql +0 -0
  156. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0014_bandwidth_tracking.sql +0 -0
  157. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0015_multi_provider.sql +0 -0
  158. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0016_requests_provider_id.sql +0 -0
  159. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0017_price_snapshots_provider_id.sql +0 -0
  160. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0018_provider_pings.sql +0 -0
  161. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0019_client_ip.sql +0 -0
  162. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0020_performance_indexes.sql +0 -0
  163. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0021_provider_model_metadata.sql +0 -0
  164. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0022_dashboard_indexes.sql +0 -0
  165. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0023_deprecated_model_placeholder.sql +0 -0
  166. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0024_account_backoffs.sql +0 -0
  167. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0025_stale_request_index.sql +0 -0
  168. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0026_attempt_observability.sql +0 -0
  169. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0027_routing_decisions.sql +0 -0
  170. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0028_operational_events.sql +0 -0
  171. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0029_latency_phases.sql +0 -0
  172. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0030_model_pricing_aliases.sql +0 -0
  173. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0031_price_snapshot_provenance.sql +0 -0
  174. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0032_usage_rollups.sql +0 -0
  175. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0033_request_provider_local_cost.sql +0 -0
  176. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0034_transcoding_daily.sql +0 -0
  177. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/0035_routing_decision_score_components.sql +0 -0
  178. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/db/schema/checksums.json +0 -0
  179. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/deploy/__init__.py +0 -0
  180. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/deploy_user.py +0 -0
  181. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/errors.py +0 -0
  182. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/fastcli.py +0 -0
  183. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/health/__init__.py +0 -0
  184. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/health/backoff.py +0 -0
  185. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/health/circuit_breaker.py +0 -0
  186. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/health/health_manager.py +0 -0
  187. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/integrations/__init__.py +0 -0
  188. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/integrations/opencode.py +0 -0
  189. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/lifecycle/__init__.py +0 -0
  190. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/lifecycle/backup.py +0 -0
  191. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/lifecycle/uninstall.py +0 -0
  192. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/logging.py +0 -0
  193. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/metrics/__init__.py +0 -0
  194. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/metrics/buffer.py +0 -0
  195. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/models/__init__.py +0 -0
  196. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/models/api.py +0 -0
  197. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/models/config.py +0 -0
  198. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/models/database.py +0 -0
  199. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/models/domain.py +0 -0
  200. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/onboard.py +0 -0
  201. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/providers/__init__.py +0 -0
  202. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/providers/_templates.toml +0 -0
  203. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/providers/auth.py +0 -0
  204. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/providers/client_pool.py +0 -0
  205. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/providers/contract.py +0 -0
  206. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/providers/dns_cache.py +0 -0
  207. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/providers/outbound.py +0 -0
  208. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/providers/pproxy_transport.py +0 -0
  209. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/proxy/__init__.py +0 -0
  210. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/proxy/client.py +0 -0
  211. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/proxy/cost_reporting.py +0 -0
  212. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/proxy/sse_observer.py +0 -0
  213. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/proxy/usage.py +0 -0
  214. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/py.typed +0 -0
  215. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/quota/__init__.py +0 -0
  216. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/quota/audit.py +0 -0
  217. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/quota/estimation.py +0 -0
  218. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/quota/reservation.py +0 -0
  219. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/quota/scorer.py +0 -0
  220. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/request/__init__.py +0 -0
  221. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/request/attempt_finalizer.py +0 -0
  222. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/request/body.py +0 -0
  223. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/request/coordinator.py +0 -0
  224. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/request/finalizer.py +0 -0
  225. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/request/limits.py +0 -0
  226. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/retry/__init__.py +0 -0
  227. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/retry/classification.py +0 -0
  228. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/routing/__init__.py +0 -0
  229. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/routing/eligibility.py +0 -0
  230. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/routing/provider.py +0 -0
  231. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/routing/router.py +0 -0
  232. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/runtime.py +0 -0
  233. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/runtime_dispatch.py +0 -0
  234. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/runtime_metrics.py +0 -0
  235. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/runtime_paths.py +0 -0
  236. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/security/__init__.py +0 -0
  237. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/security/redaction.py +0 -0
  238. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/stats/__init__.py +0 -0
  239. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/stats/grouped_timeseries.py +0 -0
  240. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/stats/queries.py +0 -0
  241. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/stats/service.py +0 -0
  242. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/toml_edit.py +0 -0
  243. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/transcoder/__init__.py +0 -0
  244. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/transcoder/anthropic_to_openai.py +0 -0
  245. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/transcoder/context.py +0 -0
  246. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/transcoder/errors.py +0 -0
  247. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/transcoder/ids.py +0 -0
  248. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/transcoder/policy.py +0 -0
  249. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/transcoder/protocol.py +0 -0
  250. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/transcoder/static_headers.py +0 -0
  251. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/transcoder/usage.py +0 -0
  252. {eggpool-0.3.7 → eggpool-0.3.9}/src/eggpool/update_checker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: eggpool
3
- Version: 0.3.7
3
+ Version: 0.3.9
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "eggpool"
3
- version = "0.3.7"
3
+ version = "0.3.9"
4
4
  description = "A lightweight proxy that aggregates multiple LLM provider accounts behind one OpenAI-compatible endpoint"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -106,6 +106,59 @@ class CatalogFetchError(RuntimeError):
106
106
  """Raised when a catalog fetch fails (network, parse, auth)."""
107
107
 
108
108
 
109
+ def _safe_parse_catalog_price_per_1k(
110
+ *,
111
+ catalog_name: str,
112
+ model_id: str,
113
+ category: str,
114
+ value: object,
115
+ default_unit: str = "1k",
116
+ ) -> float | None:
117
+ """Parse an untrusted external catalog price field.
118
+
119
+ External catalogs are advisory. A malformed or negative field should
120
+ remove that category from the resolved pricing, not fail the whole
121
+ refresh cycle or discard other valid rates from the same model row.
122
+ """
123
+ from eggpool.catalog.pricing import parse_price_per_1k
124
+
125
+ try:
126
+ return parse_price_per_1k(value, default_unit=default_unit)
127
+ except Exception as exc:
128
+ logger.warning(
129
+ "Ignoring invalid %s %s price for %s: %s",
130
+ catalog_name,
131
+ category,
132
+ model_id,
133
+ exc,
134
+ )
135
+ return None
136
+
137
+
138
+ def _safe_parse_catalog_microdollars(
139
+ *,
140
+ catalog_name: str,
141
+ model_id: str,
142
+ category: str,
143
+ value: object,
144
+ default_unit: str | None = None,
145
+ ) -> int | None:
146
+ """Parse an untrusted external catalog cache-rate field."""
147
+ from eggpool.catalog.pricing import parse_microdollars_per_million
148
+
149
+ try:
150
+ return parse_microdollars_per_million(value, default_unit=default_unit)
151
+ except Exception as exc:
152
+ logger.warning(
153
+ "Ignoring invalid %s %s price for %s: %s",
154
+ catalog_name,
155
+ category,
156
+ model_id,
157
+ exc,
158
+ )
159
+ return None
160
+
161
+
109
162
  class CatalogHttpClient(Protocol):
110
163
  """Minimal async HTTP client interface used by catalog resolvers."""
111
164
 
@@ -224,8 +277,13 @@ class OpenRouterCatalogResolver:
224
277
  payload_obj: object = response.json()
225
278
  except (httpx.HTTPError, ValueError) as exc:
226
279
  raise CatalogFetchError(f"OpenRouter fetch failed: {exc}") from exc
227
- entries = self._parse_catalog(payload_obj)
228
- self._cache.store(entries)
280
+ try:
281
+ entries = self._parse_catalog(payload_obj)
282
+ self._cache.store(entries)
283
+ except Exception as exc:
284
+ raise CatalogFetchError(
285
+ f"OpenRouter catalog parse failed: {exc}"
286
+ ) from exc
229
287
  return entries
230
288
 
231
289
  @staticmethod
@@ -249,8 +307,13 @@ class OpenRouterCatalogResolver:
249
307
  entry = OpenRouterCatalogResolver._parse_entry(
250
308
  model_id_obj, pricing_obj, raw_dict
251
309
  )
252
- except (ValueError, TypeError) as exc:
253
- logger.warning("Skipping catalog entry %r: %s", model_id_obj, exc)
310
+ except Exception as exc:
311
+ logger.warning(
312
+ "Skipping catalog entry %r after unexpected parse failure: %s",
313
+ model_id_obj,
314
+ exc,
315
+ exc_info=True,
316
+ )
254
317
  continue
255
318
  entries[model_id_obj] = entry
256
319
  # Strip raw upstream JSON to bound the cache footprint;
@@ -264,27 +327,40 @@ class OpenRouterCatalogResolver:
264
327
  model_id: str, pricing: object, raw: dict[str, Any]
265
328
  ) -> CatalogEntry:
266
329
  """Translate an OpenRouter pricing block into a CatalogEntry."""
267
- from eggpool.catalog.pricing import (
268
- parse_microdollars_per_million,
269
- parse_price_per_1k,
270
- )
271
330
 
272
331
  def _opt(key: str) -> object | None:
273
332
  if not isinstance(pricing, dict):
274
333
  return None
275
- value: object = pricing.get(key) # type: ignore[reportUnknownMemberType]
276
- if isinstance(value, (str, int, float)):
277
- return value
278
- return None
334
+ return pricing.get(key) # type: ignore[reportUnknownMemberType]
279
335
 
280
336
  # OpenRouter fields are dollars-per-token numeric strings.
281
- input_per_1k = parse_price_per_1k(_opt("prompt"), default_unit="token")
282
- output_per_1k = parse_price_per_1k(_opt("completion"), default_unit="token")
283
- cache_read = parse_microdollars_per_million(
284
- _opt("input_cache_read"), default_unit="token"
337
+ input_per_1k = _safe_parse_catalog_price_per_1k(
338
+ catalog_name=OpenRouterCatalogResolver.name,
339
+ model_id=model_id,
340
+ category="input",
341
+ value=_opt("prompt"),
342
+ default_unit="token",
343
+ )
344
+ output_per_1k = _safe_parse_catalog_price_per_1k(
345
+ catalog_name=OpenRouterCatalogResolver.name,
346
+ model_id=model_id,
347
+ category="output",
348
+ value=_opt("completion"),
349
+ default_unit="token",
350
+ )
351
+ cache_read = _safe_parse_catalog_microdollars(
352
+ catalog_name=OpenRouterCatalogResolver.name,
353
+ model_id=model_id,
354
+ category="cache_read",
355
+ value=_opt("input_cache_read"),
356
+ default_unit="token",
285
357
  )
286
- cache_write = parse_microdollars_per_million(
287
- _opt("input_cache_write"), default_unit="token"
358
+ cache_write = _safe_parse_catalog_microdollars(
359
+ catalog_name=OpenRouterCatalogResolver.name,
360
+ model_id=model_id,
361
+ category="cache_write",
362
+ value=_opt("input_cache_write"),
363
+ default_unit="token",
288
364
  )
289
365
  return CatalogEntry(
290
366
  catalog_model_id=model_id,
@@ -375,6 +451,15 @@ class CatalogResolverPipeline:
375
451
  exc,
376
452
  )
377
453
  continue
454
+ except Exception as exc:
455
+ logger.exception(
456
+ "Catalog %s failed unexpectedly for %s/%s: %s",
457
+ resolver.name,
458
+ provider_id,
459
+ model_id,
460
+ exc,
461
+ )
462
+ continue
378
463
  entry = catalog.get(alias.catalog_model_id)
379
464
  if entry is None:
380
465
  logger.warning(
@@ -385,10 +470,20 @@ class CatalogResolverPipeline:
385
470
  model_id,
386
471
  )
387
472
  continue
388
- return resolver.to_resolved_pricing(
389
- entry=entry,
390
- provider_id=provider_id,
391
- model_id=model_id,
392
- alias=alias,
393
- )
473
+ try:
474
+ return resolver.to_resolved_pricing(
475
+ entry=entry,
476
+ provider_id=provider_id,
477
+ model_id=model_id,
478
+ alias=alias,
479
+ )
480
+ except Exception as exc:
481
+ logger.exception(
482
+ "Catalog %s failed to convert pricing for %s/%s: %s",
483
+ resolver.name,
484
+ provider_id,
485
+ model_id,
486
+ exc,
487
+ )
488
+ continue
394
489
  return None
@@ -531,6 +531,12 @@ def _check_stale_contracts(config: AppConfig, config_path: str) -> list[str]:
531
531
 
532
532
  for provider in config.providers.values():
533
533
  endpoint = provider.models_endpoint
534
+ raw_section_obj = raw_providers.get(provider.id)
535
+ raw_section: dict[str, object] = (
536
+ cast("dict[str, object]", raw_section_obj)
537
+ if isinstance(raw_section_obj, dict)
538
+ else {}
539
+ )
534
540
 
535
541
  if (
536
542
  endpoint is not None
@@ -553,13 +559,21 @@ def _check_stale_contracts(config: AppConfig, config_path: str) -> list[str]:
553
559
  "verify.require_models is true; the contract is contradictory"
554
560
  )
555
561
 
556
- if provider.anthropic_path and "anthropic" not in provider.protocols:
562
+ if (
563
+ "anthropic_path" in raw_section
564
+ and provider.anthropic_path
565
+ and "anthropic" not in provider.protocols
566
+ ):
557
567
  warnings.append(
558
568
  f"[{provider.id}] anthropic_path is set but 'anthropic' is "
559
569
  "not in protocols; the field will be ignored"
560
570
  )
561
571
 
562
- if provider.openai_path and "openai" not in provider.protocols:
572
+ if (
573
+ "openai_path" in raw_section
574
+ and provider.openai_path
575
+ and "openai" not in provider.protocols
576
+ ):
563
577
  warnings.append(
564
578
  f"[{provider.id}] openai_path is set but 'openai' is not "
565
579
  "in protocols; the field will be ignored"
@@ -595,9 +609,7 @@ def _check_stale_contracts(config: AppConfig, config_path: str) -> list[str]:
595
609
  "providers typically use header='x-api-key'"
596
610
  )
597
611
 
598
- raw_section_obj = raw_providers.get(provider.id)
599
- if isinstance(raw_section_obj, dict):
600
- raw_section = cast("dict[str, object]", raw_section_obj)
612
+ if raw_section:
601
613
  has_legacy_key = (
602
614
  "models_method" in raw_section or "models_path" in raw_section
603
615
  )
@@ -15,7 +15,7 @@ from typing import Any, cast
15
15
  from eggpool.constants import DEFAULT_PROVIDER_ID
16
16
  from eggpool.providers.contract import PROVIDER_STATUS_SYMBOLS
17
17
  from eggpool.runtime import restart_server as _restart_server
18
- from eggpool.toml_edit import render_toml_value
18
+ from eggpool.toml_edit import render_toml_value, update_section_value
19
19
 
20
20
  _REGISTRY_METADATA_FIELDS = frozenset(
21
21
  {
@@ -454,10 +454,36 @@ def merge_provider_into_config(
454
454
  )
455
455
  content = _insert_provider_block(content, block)
456
456
 
457
+ if _provider_needs_transcoder(provider_data):
458
+ content = _ensure_transcoder_enabled(content)
459
+
457
460
  path.write_text(content, encoding="utf-8")
458
461
  return True
459
462
 
460
463
 
464
+ def _provider_needs_transcoder(provider_data: dict[str, Any]) -> bool:
465
+ """Return whether this provider needs cross-protocol request bridging."""
466
+ protocols_value = provider_data.get("protocols")
467
+ if not isinstance(protocols_value, list):
468
+ return False
469
+ protocol_items = cast("list[object]", protocols_value)
470
+ protocols = {str(protocol) for protocol in protocol_items}
471
+ return bool(protocols) and not {"openai", "anthropic"}.issubset(protocols)
472
+
473
+
474
+ def _ensure_transcoder_enabled(content: str) -> str:
475
+ """Enable the global transcoder block for single-protocol providers."""
476
+ result = update_section_value(
477
+ content.split("\n"),
478
+ "transcoder",
479
+ "enabled",
480
+ "true",
481
+ insert_missing_key=True,
482
+ append_missing_section=True,
483
+ )
484
+ return "\n".join(result.lines)
485
+
486
+
461
487
  def _get_existing_accounts(content: str, provider_id: str) -> list[str] | None:
462
488
  """Get existing account names for a provider. Returns None if not found."""
463
489
  # TOML array-of-tables uses [[providers.X.accounts]] syntax
@@ -82,7 +82,7 @@ class OpenAIToAnthropic:
82
82
  role = str(msg.get("role", "")) # pyright: ignore[reportUnknownMemberType]
83
83
  content = msg.get("content", "") # pyright: ignore[reportUnknownMemberType]
84
84
 
85
- if role == "system":
85
+ if role in ("system", "developer"):
86
86
  if isinstance(content, str):
87
87
  system_parts.append(content)
88
88
  elif isinstance(content, list):
@@ -147,6 +147,8 @@ class OpenAIToAnthropic:
147
147
  out["temperature"] = temperature
148
148
 
149
149
  max_tokens = payload.get("max_tokens")
150
+ if max_tokens is None:
151
+ max_tokens = payload.get("max_completion_tokens")
150
152
  if max_tokens is None:
151
153
  max_tokens = 4096
152
154
  warnings.append(
@@ -510,6 +510,7 @@ class AnthropicToOpenAIStreaming(_BaseStreamingTranscoder):
510
510
  self._id = ""
511
511
  self._model = ""
512
512
  self._emitted_usage = False
513
+ self._done_emitted = False
513
514
 
514
515
  async def feed(self, chunk: bytes) -> list[bytes]:
515
516
  self._observer.observe(chunk)
@@ -525,7 +526,9 @@ class AnthropicToOpenAIStreaming(_BaseStreamingTranscoder):
525
526
  out: list[bytes] = []
526
527
  for event_type, data in frames:
527
528
  out.extend(self._translate(event_type, data))
528
- out.append(self._openai_done())
529
+ done = self._emit_done()
530
+ if done is not None:
531
+ out.append(done)
529
532
  return out
530
533
 
531
534
  def _translate(
@@ -550,7 +553,7 @@ class AnthropicToOpenAIStreaming(_BaseStreamingTranscoder):
550
553
  msg = str(err_typed.get("message", str(err_typed)))
551
554
  else:
552
555
  msg = str(err)
553
- return [
556
+ out = [
554
557
  self._openai_frame(
555
558
  {
556
559
  "error": {
@@ -561,8 +564,11 @@ class AnthropicToOpenAIStreaming(_BaseStreamingTranscoder):
561
564
  },
562
565
  },
563
566
  ),
564
- self._openai_done(),
565
567
  ]
568
+ done = self._emit_done()
569
+ if done is not None:
570
+ out.append(done)
571
+ return out
566
572
 
567
573
  def _dispatch(
568
574
  self,
@@ -714,9 +720,18 @@ class AnthropicToOpenAIStreaming(_BaseStreamingTranscoder):
714
720
  },
715
721
  ),
716
722
  )
717
- out.append(self._openai_done())
723
+ done = self._emit_done()
724
+ if done is not None:
725
+ out.append(done)
718
726
  return out
719
727
 
728
+ def _emit_done(self) -> bytes | None:
729
+ """Emit the OpenAI terminal marker at most once."""
730
+ if self._done_emitted:
731
+ return None
732
+ self._done_emitted = True
733
+ return self._openai_done()
734
+
720
735
 
721
736
  def select_streaming_transcoder(
722
737
  *,
@@ -196,7 +196,7 @@ wheels = [
196
196
 
197
197
  [[package]]
198
198
  name = "eggpool"
199
- version = "0.3.7"
199
+ version = "0.3.9"
200
200
  source = { editable = "." }
201
201
  dependencies = [
202
202
  { name = "aiosqlite" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes