litellm-enterprise 0.1.25__tar.gz → 0.1.26__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 (130) hide show
  1. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/PKG-INFO +1 -1
  2. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/send_emails/base_email.py +138 -2
  3. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/send_emails/resend_email.py +2 -1
  4. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/send_emails/sendgrid_email.py +2 -1
  5. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/send_emails/smtp_email.py +2 -1
  6. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/proxy/hooks/managed_files.py +155 -4
  7. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/types/enterprise_callbacks/send_emails.py +2 -0
  8. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/pyproject.toml +2 -2
  9. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/LICENSE.md +0 -0
  10. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/README.md +0 -0
  11. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/__init__.py +0 -0
  12. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/callback_controls.py +0 -0
  13. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/example_logging_api.py +0 -0
  14. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/llama_guard.py +0 -0
  15. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/llm_guard.py +0 -0
  16. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/pagerduty/pagerduty.py +0 -0
  17. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secret_detection.py +0 -0
  18. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/__init__.py +0 -0
  19. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/adafruit.py +0 -0
  20. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/adobe.py +0 -0
  21. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/age_secret_key.py +0 -0
  22. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/airtable_api_key.py +0 -0
  23. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/algolia_api_key.py +0 -0
  24. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/alibaba.py +0 -0
  25. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/asana.py +0 -0
  26. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/atlassian_api_token.py +0 -0
  27. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/authress_access_key.py +0 -0
  28. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/beamer_api_token.py +0 -0
  29. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/bitbucket.py +0 -0
  30. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/bittrex.py +0 -0
  31. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/clojars_api_token.py +0 -0
  32. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/codecov_access_token.py +0 -0
  33. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/coinbase_access_token.py +0 -0
  34. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/confluent.py +0 -0
  35. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/contentful_api_token.py +0 -0
  36. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/databricks_api_token.py +0 -0
  37. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/datadog_access_token.py +0 -0
  38. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/defined_networking_api_token.py +0 -0
  39. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/digitalocean.py +0 -0
  40. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/discord.py +0 -0
  41. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/doppler_api_token.py +0 -0
  42. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/droneci_access_token.py +0 -0
  43. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/dropbox.py +0 -0
  44. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/duffel_api_token.py +0 -0
  45. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/dynatrace_api_token.py +0 -0
  46. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/easypost.py +0 -0
  47. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/etsy_access_token.py +0 -0
  48. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/facebook_access_token.py +0 -0
  49. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/fastly_api_token.py +0 -0
  50. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/finicity.py +0 -0
  51. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/finnhub_access_token.py +0 -0
  52. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/flickr_access_token.py +0 -0
  53. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/flutterwave.py +0 -0
  54. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/frameio_api_token.py +0 -0
  55. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/freshbooks_access_token.py +0 -0
  56. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/gcp_api_key.py +0 -0
  57. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/github_token.py +0 -0
  58. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/gitlab.py +0 -0
  59. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/gitter_access_token.py +0 -0
  60. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/gocardless_api_token.py +0 -0
  61. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/grafana.py +0 -0
  62. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/hashicorp_tf_api_token.py +0 -0
  63. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/heroku_api_key.py +0 -0
  64. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/hubspot_api_key.py +0 -0
  65. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/huggingface.py +0 -0
  66. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/intercom_api_key.py +0 -0
  67. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/jfrog.py +0 -0
  68. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/jwt.py +0 -0
  69. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/kraken_access_token.py +0 -0
  70. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/kucoin.py +0 -0
  71. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/launchdarkly_access_token.py +0 -0
  72. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/linear.py +0 -0
  73. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/linkedin.py +0 -0
  74. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/lob.py +0 -0
  75. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/mailgun.py +0 -0
  76. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/mapbox_api_token.py +0 -0
  77. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/mattermost_access_token.py +0 -0
  78. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/messagebird.py +0 -0
  79. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/microsoft_teams_webhook.py +0 -0
  80. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/netlify_access_token.py +0 -0
  81. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/new_relic.py +0 -0
  82. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/nytimes_access_token.py +0 -0
  83. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/okta_access_token.py +0 -0
  84. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/openai_api_key.py +0 -0
  85. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/planetscale.py +0 -0
  86. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/postman_api_token.py +0 -0
  87. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/prefect_api_token.py +0 -0
  88. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/pulumi_api_token.py +0 -0
  89. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/pypi_upload_token.py +0 -0
  90. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/rapidapi_access_token.py +0 -0
  91. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/readme_api_token.py +0 -0
  92. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/rubygems_api_token.py +0 -0
  93. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/scalingo_api_token.py +0 -0
  94. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/sendbird.py +0 -0
  95. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/sendgrid_api_token.py +0 -0
  96. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/sendinblue_api_token.py +0 -0
  97. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/sentry_access_token.py +0 -0
  98. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/shippo_api_token.py +0 -0
  99. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/shopify.py +0 -0
  100. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/slack.py +0 -0
  101. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/snyk_api_token.py +0 -0
  102. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/squarespace_access_token.py +0 -0
  103. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/sumologic.py +0 -0
  104. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/telegram_bot_api_token.py +0 -0
  105. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/travisci_access_token.py +0 -0
  106. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/twitch_api_token.py +0 -0
  107. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/twitter.py +0 -0
  108. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/typeform_api_token.py +0 -0
  109. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/vault.py +0 -0
  110. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/yandex.py +0 -0
  111. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/secrets_plugins/zendesk_secret_key.py +0 -0
  112. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/enterprise_callbacks/send_emails/endpoints.py +0 -0
  113. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/integrations/custom_guardrail.py +0 -0
  114. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/litellm_core_utils/litellm_logging.py +0 -0
  115. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/proxy/audit_logging_endpoints.py +0 -0
  116. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/proxy/auth/__init__.py +0 -0
  117. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/proxy/auth/custom_sso_handler.py +0 -0
  118. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/proxy/auth/route_checks.py +0 -0
  119. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/proxy/auth/user_api_key_auth.py +0 -0
  120. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/proxy/common_utils/check_batch_cost.py +0 -0
  121. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/proxy/enterprise_routes.py +0 -0
  122. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/proxy/management_endpoints/__init__.py +0 -0
  123. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/proxy/management_endpoints/internal_user_endpoints.py +0 -0
  124. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/proxy/management_endpoints/key_management_endpoints.py +0 -0
  125. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/proxy/proxy_server.py +0 -0
  126. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/proxy/readme.md +0 -0
  127. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/proxy/utils.py +0 -0
  128. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/proxy/vector_stores/endpoints.py +0 -0
  129. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/types/proxy/audit_logging_endpoints.py +0 -0
  130. {litellm_enterprise-0.1.25 → litellm_enterprise-0.1.26}/litellm_enterprise/types/proxy/proxy_server.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: litellm-enterprise
3
- Version: 0.1.25
3
+ Version: 0.1.26
4
4
  Summary: Package for LiteLLM Enterprise features
5
5
  License-File: LICENSE.md
6
6
  Author: BerriAI
@@ -5,7 +5,7 @@ Base class for sending emails to user after creating keys or invite links
5
5
 
6
6
  import json
7
7
  import os
8
- from typing import List, Optional
8
+ from typing import List, Literal, Optional
9
9
 
10
10
  from litellm_enterprise.types.enterprise_callbacks.send_emails import (
11
11
  EmailEvent,
@@ -15,6 +15,7 @@ from litellm_enterprise.types.enterprise_callbacks.send_emails import (
15
15
  )
16
16
 
17
17
  from litellm._logging import verbose_proxy_logger
18
+ from litellm.caching.caching import DualCache
18
19
  from litellm.integrations.custom_logger import CustomLogger
19
20
  from litellm.integrations.email_templates.email_footer import EMAIL_FOOTER
20
21
  from litellm.integrations.email_templates.key_created_email import (
@@ -26,7 +27,10 @@ from litellm.integrations.email_templates.key_rotated_email import (
26
27
  from litellm.integrations.email_templates.user_invitation_email import (
27
28
  USER_INVITATION_EMAIL_TEMPLATE,
28
29
  )
29
- from litellm.proxy._types import InvitationNew, UserAPIKeyAuth, WebhookEvent
30
+ from litellm.integrations.email_templates.templates import (
31
+ SOFT_BUDGET_ALERT_EMAIL_TEMPLATE,
32
+ )
33
+ from litellm.proxy._types import CallInfo, InvitationNew, UserAPIKeyAuth, WebhookEvent
30
34
  from litellm.secret_managers.main import get_secret_bool
31
35
  from litellm.types.integrations.slack_alerting import LITELLM_LOGO_URL
32
36
 
@@ -39,6 +43,22 @@ class BaseEmailLogger(CustomLogger):
39
43
  EmailEvent.virtual_key_created: "LiteLLM: {event_message}",
40
44
  EmailEvent.virtual_key_rotated: "LiteLLM: {event_message}",
41
45
  }
46
+ DEFAULT_BUDGET_ALERT_TTL = 24 * 60 * 60 # 24 hours in seconds
47
+
48
+ def __init__(
49
+ self,
50
+ internal_usage_cache: Optional[DualCache] = None,
51
+ **kwargs,
52
+ ):
53
+ """
54
+ Initialize BaseEmailLogger
55
+
56
+ Args:
57
+ internal_usage_cache: DualCache instance for preventing duplicate alerts
58
+ **kwargs: Additional arguments passed to CustomLogger
59
+ """
60
+ super().__init__(**kwargs)
61
+ self.internal_usage_cache = internal_usage_cache or DualCache()
42
62
 
43
63
  async def send_user_invitation_email(self, event: WebhookEvent):
44
64
  """
@@ -154,6 +174,122 @@ class BaseEmailLogger(CustomLogger):
154
174
  )
155
175
  pass
156
176
 
177
+ async def send_soft_budget_alert_email(self, event: WebhookEvent):
178
+ """
179
+ Send email to user when soft budget is crossed
180
+ """
181
+ email_params = await self._get_email_params(
182
+ email_event=EmailEvent.soft_budget_crossed, # Reuse existing event type for subject template
183
+ user_id=event.user_id,
184
+ user_email=event.user_email,
185
+ event_message=event.event_message,
186
+ )
187
+
188
+ verbose_proxy_logger.debug(
189
+ f"send_soft_budget_alert_email_event: {json.dumps(event.model_dump(exclude_none=True), indent=4, default=str)}"
190
+ )
191
+
192
+ # Format budget values
193
+ soft_budget_str = f"${event.soft_budget}" if event.soft_budget is not None else "N/A"
194
+ spend_str = f"${event.spend}" if event.spend is not None else "$0.00"
195
+ max_budget_info = ""
196
+ if event.max_budget is not None:
197
+ max_budget_info = f"<b>Maximum Budget:</b> ${event.max_budget} <br />"
198
+
199
+ email_html_content = SOFT_BUDGET_ALERT_EMAIL_TEMPLATE.format(
200
+ email_logo_url=email_params.logo_url,
201
+ recipient_email=email_params.recipient_email,
202
+ soft_budget=soft_budget_str,
203
+ spend=spend_str,
204
+ max_budget_info=max_budget_info,
205
+ base_url=email_params.base_url,
206
+ email_support_contact=email_params.support_contact,
207
+ )
208
+ await self.send_email(
209
+ from_email=self.DEFAULT_LITELLM_EMAIL,
210
+ to_email=[email_params.recipient_email],
211
+ subject=email_params.subject,
212
+ html_body=email_html_content,
213
+ )
214
+ pass
215
+
216
+ async def budget_alerts(
217
+ self,
218
+ type: Literal[
219
+ "token_budget",
220
+ "soft_budget",
221
+ "user_budget",
222
+ "team_budget",
223
+ "organization_budget",
224
+ "proxy_budget",
225
+ "projected_limit_exceeded",
226
+ ],
227
+ user_info: CallInfo,
228
+ ):
229
+ """
230
+ Send a budget alert via email
231
+
232
+ Args:
233
+ type: The type of budget alert to send
234
+ user_info: The user info to send the alert for
235
+ """
236
+ ## PREVENTITIVE ALERTING ##
237
+ # - Alert once within 24hr period
238
+ # - Cache this information
239
+ # - Don't re-alert, if alert already sent
240
+ _cache: DualCache = self.internal_usage_cache
241
+
242
+ # percent of max_budget left to spend
243
+ if user_info.max_budget is None and user_info.soft_budget is None:
244
+ return
245
+
246
+ # For soft_budget alerts, check if we've already sent an alert
247
+ if type == "soft_budget":
248
+ if user_info.soft_budget is not None and user_info.spend >= user_info.soft_budget:
249
+ # Generate cache key based on event type and identifier
250
+ _id = user_info.token or user_info.user_id or "default_id"
251
+ _cache_key = f"email_budget_alerts:soft_budget_crossed:{_id}"
252
+
253
+ # Check if we've already sent this alert
254
+ result = await _cache.async_get_cache(key=_cache_key)
255
+ if result is None:
256
+ # Create WebhookEvent for soft budget alert
257
+ event_message = f"Soft Budget Crossed - Total Soft Budget: ${user_info.soft_budget}"
258
+ webhook_event = WebhookEvent(
259
+ event="soft_budget_crossed",
260
+ event_message=event_message,
261
+ spend=user_info.spend,
262
+ max_budget=user_info.max_budget,
263
+ soft_budget=user_info.soft_budget,
264
+ token=user_info.token,
265
+ customer_id=user_info.customer_id,
266
+ user_id=user_info.user_id,
267
+ team_id=user_info.team_id,
268
+ team_alias=user_info.team_alias,
269
+ organization_id=user_info.organization_id,
270
+ user_email=user_info.user_email,
271
+ key_alias=user_info.key_alias,
272
+ projected_exceeded_date=user_info.projected_exceeded_date,
273
+ projected_spend=user_info.projected_spend,
274
+ event_group=user_info.event_group,
275
+ )
276
+
277
+ try:
278
+ await self.send_soft_budget_alert_email(webhook_event)
279
+
280
+ # Cache the alert to prevent duplicate sends
281
+ await _cache.async_set_cache(
282
+ key=_cache_key,
283
+ value="SENT",
284
+ ttl=self.DEFAULT_BUDGET_ALERT_TTL,
285
+ )
286
+ except Exception as e:
287
+ verbose_proxy_logger.error(
288
+ f"Error sending soft budget alert email: {e}",
289
+ exc_info=True,
290
+ )
291
+ return
292
+
157
293
  async def _get_email_params(
158
294
  self,
159
295
  email_event: EmailEvent,
@@ -19,7 +19,8 @@ RESEND_API_ENDPOINT = "https://api.resend.com/emails"
19
19
 
20
20
 
21
21
  class ResendEmailLogger(BaseEmailLogger):
22
- def __init__(self):
22
+ def __init__(self, internal_usage_cache=None, **kwargs):
23
+ super().__init__(internal_usage_cache=internal_usage_cache, **kwargs)
23
24
  self.async_httpx_client = get_async_httpx_client(
24
25
  llm_provider=httpxSpecialProvider.LoggingCallback
25
26
  )
@@ -27,7 +27,8 @@ class SendGridEmailLogger(BaseEmailLogger):
27
27
  - SENDGRID_API_KEY
28
28
  """
29
29
 
30
- def __init__(self):
30
+ def __init__(self, internal_usage_cache=None, **kwargs):
31
+ super().__init__(internal_usage_cache=internal_usage_cache, **kwargs)
31
32
  self.async_httpx_client = get_async_httpx_client(
32
33
  llm_provider=httpxSpecialProvider.LoggingCallback
33
34
  )
@@ -21,7 +21,8 @@ class SMTPEmailLogger(BaseEmailLogger):
21
21
  - SMTP_SENDER_EMAIL
22
22
  """
23
23
 
24
- def __init__(self):
24
+ def __init__(self, internal_usage_cache=None, **kwargs):
25
+ super().__init__(internal_usage_cache=internal_usage_cache, **kwargs)
25
26
  verbose_logger.debug("SMTP Email Logger initialized....")
26
27
 
27
28
  async def send_email(
@@ -22,7 +22,6 @@ from litellm.proxy._types import (
22
22
  )
23
23
  from litellm.proxy.openai_files_endpoints.common_utils import (
24
24
  _is_base64_encoded_unified_file_id,
25
- convert_b64_uid_to_unified_uid,
26
25
  get_batch_id_from_unified_batch_id,
27
26
  get_model_id_from_unified_batch_id,
28
27
  )
@@ -42,6 +41,10 @@ from litellm.types.utils import (
42
41
  LLMResponseTypes,
43
42
  SpecialEnums,
44
43
  )
44
+ from litellm.proxy.openai_files_endpoints.common_utils import (
45
+ get_content_type_from_file_object,
46
+ normalize_mime_type_for_provider,
47
+ )
45
48
 
46
49
  if TYPE_CHECKING:
47
50
  from litellm.types.llms.openai import HttpxBinaryResponseContent
@@ -108,6 +111,17 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints):
108
111
 
109
112
  if file_object is not None:
110
113
  db_data["file_object"] = file_object.model_dump_json()
114
+ # Extract storage metadata from hidden params if present
115
+ hidden_params = getattr(file_object, "_hidden_params", {}) or {}
116
+ if "storage_backend" in hidden_params:
117
+ db_data["storage_backend"] = hidden_params["storage_backend"]
118
+ if "storage_url" in hidden_params:
119
+ db_data["storage_url"] = hidden_params["storage_url"]
120
+
121
+ verbose_logger.debug(
122
+ f"Storage metadata: storage_backend={db_data.get('storage_backend')}, "
123
+ f"storage_url={db_data.get('storage_url')}"
124
+ )
111
125
 
112
126
  result = await self.prisma_client.db.litellm_managedfiletable.create(
113
127
  data=db_data
@@ -268,7 +282,7 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints):
268
282
  )
269
283
  return False
270
284
 
271
- async def async_pre_call_hook(
285
+ async def async_pre_call_hook( # noqa: PLR0915
272
286
  self,
273
287
  user_api_key_dict: UserAPIKeyAuth,
274
288
  cache: DualCache,
@@ -287,15 +301,31 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints):
287
301
  await self.check_managed_file_id_access(data, user_api_key_dict)
288
302
 
289
303
  ### HANDLE TRANSFORMATIONS ###
290
- if call_type == CallTypes.completion.value:
304
+ # Check both completion and acompletion call types
305
+ is_completion_call = (
306
+ call_type == CallTypes.completion.value
307
+ or call_type == CallTypes.acompletion.value
308
+ )
309
+
310
+ if is_completion_call:
291
311
  messages = data.get("messages")
312
+ model = data.get("model", "")
292
313
  if messages:
293
314
  file_ids = self.get_file_ids_from_messages(messages)
294
315
  if file_ids:
316
+ # Check if any files are stored in storage backends and need base64 conversion
317
+ # This is needed for Vertex AI/Gemini which requires base64 content
318
+ is_vertex_ai = model and ("vertex_ai" in model or "gemini" in model.lower())
319
+ if is_vertex_ai:
320
+ await self._convert_storage_files_to_base64(
321
+ messages=messages,
322
+ file_ids=file_ids,
323
+ litellm_parent_otel_span=user_api_key_dict.parent_otel_span,
324
+ )
325
+
295
326
  model_file_id_mapping = await self.get_model_file_id_mapping(
296
327
  file_ids, user_api_key_dict.parent_otel_span
297
328
  )
298
-
299
329
  data["model_file_id_mapping"] = model_file_id_mapping
300
330
  elif call_type == CallTypes.aresponses.value or call_type == CallTypes.responses.value:
301
331
  # Handle managed files in responses API input
@@ -865,3 +895,124 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints):
865
895
  )
866
896
  else:
867
897
  raise Exception(f"LiteLLM Managed File object with id={file_id} not found")
898
+
899
+ async def _convert_storage_files_to_base64(
900
+ self,
901
+ messages: List[AllMessageValues],
902
+ file_ids: List[str],
903
+ litellm_parent_otel_span: Optional[Span],
904
+ ) -> None:
905
+ """
906
+ Convert files stored in storage backends to base64 format for Vertex AI/Gemini.
907
+
908
+ This method checks if any managed files are stored in storage backends,
909
+ downloads them, and converts them to base64 format in the messages.
910
+ """
911
+ # Check each file_id to see if it's stored in a storage backend
912
+ for file_id in file_ids:
913
+ # Check if this is a base64 encoded unified file ID
914
+ decoded_unified_file_id = _is_base64_encoded_unified_file_id(file_id)
915
+
916
+ if not decoded_unified_file_id:
917
+ continue
918
+
919
+ # Check database for storage backend info
920
+ # IMPORTANT: The database stores the base64 encoded unified_file_id (not the decoded version)
921
+ # So we query with the original file_id (which is base64 encoded)
922
+ db_file = await self.prisma_client.db.litellm_managedfiletable.find_first(
923
+ where={"unified_file_id": file_id}
924
+ )
925
+
926
+ if not db_file or not db_file.storage_backend or not db_file.storage_url:
927
+ continue
928
+
929
+ # File is stored in a storage backend, download and convert to base64
930
+ try:
931
+ from litellm.llms.base_llm.files.storage_backend_factory import get_storage_backend
932
+
933
+ storage_backend_name = db_file.storage_backend
934
+ storage_url = db_file.storage_url
935
+
936
+ # Get storage backend (uses same env vars as callback)
937
+ try:
938
+ storage_backend = get_storage_backend(storage_backend_name)
939
+ except ValueError as e:
940
+ verbose_logger.warning(
941
+ f"Storage backend '{storage_backend_name}' error for file {file_id}: {str(e)}"
942
+ )
943
+ continue
944
+
945
+ file_content = await storage_backend.download_file(storage_url)
946
+
947
+ # Determine content type from file object
948
+ content_type = self._get_content_type_from_file_object(db_file.file_object)
949
+
950
+ # Convert to base64
951
+ base64_data = base64.b64encode(file_content).decode("utf-8")
952
+ base64_data_uri = f"data:{content_type};base64,{base64_data}"
953
+
954
+ # Update messages to use base64 instead of file_id
955
+ self._update_messages_with_base64_data(messages, file_id, base64_data_uri, content_type)
956
+ except Exception as e:
957
+ verbose_logger.exception(
958
+ f"Error converting file {file_id} from storage backend to base64: {str(e)}"
959
+ )
960
+ # Continue with other files even if one fails
961
+ continue
962
+
963
+ def _get_content_type_from_file_object(self, file_object: Optional[Any]) -> str:
964
+ """
965
+ Determine content type from file object.
966
+
967
+ Uses the MIME type utility for consistent detection and normalization.
968
+
969
+ Args:
970
+ file_object: The file object from the database (can be dict, JSON string, or None)
971
+
972
+ Returns:
973
+ str: MIME type (defaults to "application/octet-stream" if cannot be determined)
974
+ """
975
+ # Use utility function for detection
976
+ content_type = get_content_type_from_file_object(file_object)
977
+
978
+ # Normalize for Gemini/Vertex AI (requires image/jpeg, not image/jpg)
979
+ content_type = normalize_mime_type_for_provider(content_type, provider="gemini")
980
+
981
+ return content_type
982
+
983
+ def _update_messages_with_base64_data(
984
+ self,
985
+ messages: List[AllMessageValues],
986
+ file_id: str,
987
+ base64_data_uri: str,
988
+ content_type: str,
989
+ ) -> None:
990
+ """
991
+ Update messages to replace file_id with base64 data URI.
992
+
993
+ Args:
994
+ messages: List of messages to update
995
+ file_id: The file ID to replace
996
+ base64_data_uri: The base64 data URI to use as replacement
997
+ content_type: The MIME type of the file (e.g., "image/jpeg", "application/pdf")
998
+ """
999
+ for message in messages:
1000
+ if message.get("role") == "user":
1001
+ content = message.get("content")
1002
+ if content and isinstance(content, list):
1003
+ for element in content:
1004
+ if element.get("type") == "file":
1005
+ file_element = cast(ChatCompletionFileObject, element)
1006
+ file_element_file = file_element.get("file", {})
1007
+
1008
+ if file_element_file.get("file_id") == file_id:
1009
+ # Replace file_id with base64 data
1010
+ file_element_file["file_data"] = base64_data_uri
1011
+ # Set format to help Gemini determine mime type
1012
+ file_element_file["format"] = content_type
1013
+ # Remove file_id to ensure only file_data is used
1014
+ file_element_file.pop("file_id", None)
1015
+
1016
+ verbose_logger.debug(
1017
+ f"Converted file {file_id} from storage backend to base64 with format {content_type}"
1018
+ )
@@ -36,6 +36,7 @@ class EmailEvent(str, enum.Enum):
36
36
  virtual_key_created = "Virtual Key Created"
37
37
  new_user_invitation = "New User Invitation"
38
38
  virtual_key_rotated = "Virtual Key Rotated"
39
+ soft_budget_crossed = "Soft Budget Crossed"
39
40
 
40
41
  class EmailEventSettings(BaseModel):
41
42
  event: EmailEvent
@@ -51,6 +52,7 @@ class DefaultEmailSettings(BaseModel):
51
52
  EmailEvent.virtual_key_created: True, # On by default
52
53
  EmailEvent.new_user_invitation: True, # On by default
53
54
  EmailEvent.virtual_key_rotated: True, # On by default
55
+ EmailEvent.soft_budget_crossed: True, # On by default
54
56
  }
55
57
  )
56
58
  def to_dict(self) -> Dict[str, bool]:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "litellm-enterprise"
3
- version = "0.1.25"
3
+ version = "0.1.26"
4
4
  description = "Package for LiteLLM Enterprise features"
5
5
  authors = ["BerriAI"]
6
6
  readme = "README.md"
@@ -22,7 +22,7 @@ requires = ["poetry-core"]
22
22
  build-backend = "poetry.core.masonry.api"
23
23
 
24
24
  [tool.commitizen]
25
- version = "0.1.25"
25
+ version = "0.1.26"
26
26
  version_files = [
27
27
  "pyproject.toml:version",
28
28
  "../requirements.txt:litellm-enterprise==",