diff --git a/examples/azure-openai/fastagent.config.yaml b/examples/azure-openai/fastagent.config.yaml new file mode 100644 index 000000000..88703f201 --- /dev/null +++ b/examples/azure-openai/fastagent.config.yaml @@ -0,0 +1,54 @@ +# NOTE: Since version X.X, the Azure OpenAI integration in FastAgent reuses the OpenAI logic. +# Authentication (API Key or DefaultAzureCredential) and connectivity are managed automatically. +# You only need to configure the 'azure' section as indicated below; the system will select the appropriate method. +# +# Example configuration for Azure OpenAI in fast-agent +# +# There are three supported authentication/configuration modes for Azure OpenAI: +# +# 1. Using 'resource_name' and 'api_key' (recommended for most users) +# 2. Using 'base_url' and 'api_key' (for custom endpoints or sovereign clouds) +# 3. Using 'base_url' and DefaultAzureCredential (for managed identity, Azure CLI, etc.) +# +# Use ONLY one of the parameters: 'resource_name' or 'base_url'. +# - If you define 'base_url', it will be used directly as the endpoint and 'resource_name' will be ignored. +# - If you define 'resource_name' (and not 'base_url'), the endpoint will be constructed automatically. +# - If both are missing, the configuration is invalid. +# +# Do not include both at the same time. + +# --- OPTION 1: Using resource_name and api_key (recommended for standard Azure) --- +default_model: "azure.my-deployment" + +azure: + api_key: "YOUR_AZURE_OPENAI_API_KEY" + resource_name: "your-resource-name" + azure_deployment: "my-deployment" + api_version: "2023-05-15" + # Do not include base_url if you use resource_name + +# --- OPTION 2: Using base_url and api_key (for custom endpoints or sovereign clouds) --- +# default_model: "azure.my-deployment" +# +# azure: +# api_key: "YOUR_AZURE_OPENAI_API_KEY" +# base_url: "https://your-resource-name.openai.azure.com/" +# azure_deployment: "my-deployment" +# api_version: "2023-05-15" +# # Do not include resource_name if you use base_url + +# --- OPTION 3: Using base_url and DefaultAzureCredential (for managed identity, Azure CLI, etc.) --- +# Requires the 'azure-identity' package to be installed. +# No api_key or resource_name should be present in this mode. +# base_url is required and must be the full endpoint URL. +# +# default_model: "azure.my-deployment" +# +# azure: +# use_default_azure_credential: true +# base_url: "https://your-resource-name.openai.azure.com/" +# azure_deployment: "my-deployment" +# api_version: "2023-05-15" +# # Do not include api_key or resource_name in this mode + +# You can add other providers or settings as needed. diff --git a/pyproject.toml b/pyproject.toml index 79235da2b..8e9b13758 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,19 +25,24 @@ dependencies = [ "typer>=0.15.1", "anthropic>=0.49.0", "openai>=1.63.2", + "azure-identity>=1.14.0", "prompt-toolkit>=3.0.50", "aiohttp>=3.11.13", "a2a-types>=0.1.0", - "opentelemetry-instrumentation-openai>=0.39.3", - "opentelemetry-instrumentation-anthropic>=0.39.3", + "opentelemetry-instrumentation-openai>=0.39.3; python_version >= '3.10' and python_version < '4.0'", + "opentelemetry-instrumentation-anthropic>=0.39.3; python_version >= '3.10' and python_version < '4.0'", "tensorzero>=2025.4.7", - "opentelemetry-instrumentation-mcp>=0.40.3", + "opentelemetry-instrumentation-mcp>=0.40.3; python_version >= '3.10' and python_version < '4.0'", ] [project.optional-dependencies] openai = [ "openai>=1.58.1", ] +# For Azure OpenAI with DefaultAzureCredential support, install with: pip install fast-agent-mcp[azure] +azure = [ + "azure-identity>=1.14.0" +] dev = [ "anthropic>=0.42.0", "pre-commit>=4.0.1", @@ -57,6 +62,10 @@ build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["src/mcp_agent"] +[[tool.poetry.packages]] +include = "mcp_agent" +from = "src" + [tool.hatch.build] include = [ "src/mcp_agent/**/*.py", diff --git a/src/mcp_agent/cli/commands/check_config.py b/src/mcp_agent/cli/commands/check_config.py index cd42d8f27..4cff71be8 100644 --- a/src/mcp_agent/cli/commands/check_config.py +++ b/src/mcp_agent/cli/commands/check_config.py @@ -92,42 +92,73 @@ def get_secrets_summary(secrets_path: Optional[Path]) -> dict: return result -def check_api_keys(secrets_summary: dict) -> dict: - """Check if API keys are configured in secrets file or environment.""" +def check_api_keys(secrets_summary: dict, config_summary: dict) -> dict: + """Check if API keys are configured in secrets file or environment, including Azure DefaultAzureCredential. + Now also checks Azure config in main config file for retrocompatibility. + """ import os - # Initialize results dict using Provider enum values results = { - provider.value: {"env": None, "config": None} + provider.value: {"env": "", "config": ""} for provider in Provider if provider != Provider.FAST_AGENT - } # Include GENERIC but exclude FAST_AGENT + } # Get secrets if available secrets = secrets_summary.get("secrets", {}) secrets_status = secrets_summary.get("status", "not_found") + # Get config if available + config = config_summary if config_summary.get("status") == "parsed" else {} + config_azure = {} + if config and "azure" in config.get("config", {}): + config_azure = config["config"]["azure"] - # Check both environment variables and config file for each provider for provider_value in results: - # Check environment variables using ProviderKeyManager - env_key_name = ProviderKeyManager.get_env_key_name(provider_value) - env_key_value = os.environ.get(env_key_name) - if env_key_value: - # Store the last 5 characters if key is long enough - if len(env_key_value) > 5: - results[provider_value]["env"] = f"...{env_key_value[-5:]}" - else: - results[provider_value]["env"] = "...***" - - # Check secrets file if it was parsed successfully - if secrets_status == "parsed": - config_key = ProviderKeyManager.get_config_file_key(provider_value, secrets) - if config_key and config_key != API_KEY_HINT_TEXT: - # Store the last 5 characters if key is long enough - if len(config_key) > 5: - results[provider_value]["config"] = f"...{config_key[-5:]}" + # Special handling for Azure: support api_key and DefaultAzureCredential + if provider_value == "azure": + # Prefer secrets if present, else fallback to config + azure_cfg = {} + if secrets_status == "parsed" and "azure" in secrets: + azure_cfg = secrets.get("azure", {}) + elif config_azure: + azure_cfg = config_azure + + use_default_cred = azure_cfg.get("use_default_azure_credential", False) + base_url = azure_cfg.get("base_url") + api_key = azure_cfg.get("api_key") + # DefaultAzureCredential mode + if use_default_cred and base_url: + results[provider_value]["config"] = "DefaultAzureCredential" + # API key mode (retrocompatible) + if api_key and api_key != API_KEY_HINT_TEXT: + if len(api_key) > 5: + if results[provider_value]["config"]: + results[provider_value]["config"] += " + api_key" + else: + results[provider_value]["config"] = f"...{api_key[-5:]}" else: - results[provider_value]["config"] = "...***" + if results[provider_value]["config"]: + results[provider_value]["config"] += " + api_key" + else: + results[provider_value]["config"] = "...***" + else: + # Check environment variables using ProviderKeyManager + env_key_name = ProviderKeyManager.get_env_key_name(provider_value) + env_key_value = os.environ.get(env_key_name) + if env_key_value: + if len(env_key_value) > 5: + results[provider_value]["env"] = f"...{env_key_value[-5:]}" + else: + results[provider_value]["env"] = "...***" + + # Check secrets file if it was parsed successfully + if secrets_status == "parsed": + config_key = ProviderKeyManager.get_config_file_key(provider_value, secrets) + if config_key and config_key != API_KEY_HINT_TEXT: + if len(config_key) > 5: + results[provider_value]["config"] = f"...{config_key[-5:]}" + else: + results[provider_value]["config"] = "...***" return results @@ -235,7 +266,7 @@ def show_check_summary() -> None: system_info = get_system_info() config_summary = get_config_summary(config_files["config"]) secrets_summary = get_secrets_summary(config_files["secrets"]) - api_keys = check_api_keys(secrets_summary) + api_keys = check_api_keys(secrets_summary, config_summary) fastagent_version = get_fastagent_version() # System info panel @@ -341,8 +372,10 @@ def show_check_summary() -> None: keys_table.add_row(provider.capitalize(), env_status, config_status, active) - console.print(Panel(keys_table, title="API Keys", border_style="blue")) - + # Print the API Keys panel (fix: this was missing) + keys_panel = Panel(keys_table, title="API Keys", border_style="blue", subtitle_align="left") + console.print(keys_panel) + # MCP Servers panel (shown after API Keys) if config_summary.get("status") == "parsed": mcp_servers = config_summary.get("mcp_servers", []) @@ -447,4 +480,4 @@ def show( def main(ctx: typer.Context) -> None: """Check and diagnose FastAgent configuration.""" if ctx.invoked_subcommand is None: - show_check_summary() \ No newline at end of file + show_check_summary() diff --git a/src/mcp_agent/config.py b/src/mcp_agent/config.py index 9f93a038a..954f72ec6 100644 --- a/src/mcp_agent/config.py +++ b/src/mcp_agent/config.py @@ -179,6 +179,20 @@ class OpenRouterSettings(BaseModel): model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True) +class AzureSettings(BaseModel): + """ + Settings for using Azure OpenAI Service in the fast-agent application. + """ + + api_key: str | None = None + resource_name: str | None = None + azure_deployment: str | None = None + api_version: str | None = None + base_url: str | None = None # Optional, can be constructed from resource_name + + model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True) + + class OpenTelemetrySettings(BaseModel): """ OTEL settings for the fast-agent application. @@ -302,6 +316,9 @@ class Settings(BaseSettings): tensorzero: Optional[TensorZeroSettings] = None """Settings for using TensorZero inference gateway""" + azure: AzureSettings | None = None + """Settings for using Azure OpenAI Service in the fast-agent application""" + logger: LoggerSettings | None = LoggerSettings() """Logger settings for the fast-agent application""" diff --git a/src/mcp_agent/llm/model_factory.py b/src/mcp_agent/llm/model_factory.py index d6d61e1bb..44d135613 100644 --- a/src/mcp_agent/llm/model_factory.py +++ b/src/mcp_agent/llm/model_factory.py @@ -10,6 +10,7 @@ from mcp_agent.llm.augmented_llm_playback import PlaybackLLM from mcp_agent.llm.provider_types import Provider from mcp_agent.llm.providers.augmented_llm_anthropic import AnthropicAugmentedLLM +from mcp_agent.llm.providers.augmented_llm_azure import AzureOpenAIAugmentedLLM from mcp_agent.llm.providers.augmented_llm_deepseek import DeepSeekAugmentedLLM from mcp_agent.llm.providers.augmented_llm_generic import GenericAugmentedLLM from mcp_agent.llm.providers.augmented_llm_google import GoogleAugmentedLLM @@ -113,6 +114,7 @@ class ModelFactory: Provider.GOOGLE: GoogleAugmentedLLM, # type: ignore Provider.OPENROUTER: OpenRouterAugmentedLLM, Provider.TENSORZERO: TensorZeroAugmentedLLM, + Provider.AZURE: AzureOpenAIAugmentedLLM, } # Mapping of special model names to their specific LLM classes diff --git a/src/mcp_agent/llm/provider_types.py b/src/mcp_agent/llm/provider_types.py index 9316101a0..0e8b94d1a 100644 --- a/src/mcp_agent/llm/provider_types.py +++ b/src/mcp_agent/llm/provider_types.py @@ -16,3 +16,4 @@ class Provider(Enum): GENERIC = "generic" OPENROUTER = "openrouter" TENSORZERO = "tensorzero" # For TensorZero Gateway + AZURE = "azure" # Azure OpenAI Service diff --git a/src/mcp_agent/llm/providers/augmented_llm_azure.py b/src/mcp_agent/llm/providers/augmented_llm_azure.py new file mode 100644 index 000000000..0494d4f22 --- /dev/null +++ b/src/mcp_agent/llm/providers/augmented_llm_azure.py @@ -0,0 +1,137 @@ +from openai import AuthenticationError, AzureOpenAI, OpenAI + +from mcp_agent.core.exceptions import ProviderKeyError +from mcp_agent.llm.provider_types import Provider +from mcp_agent.llm.providers.augmented_llm_openai import OpenAIAugmentedLLM + +try: + from azure.identity import DefaultAzureCredential +except ImportError: + DefaultAzureCredential = None + + +def _extract_resource_name(url: str) -> str | None: + from urllib.parse import urlparse + + host = urlparse(url).hostname or "" + suffix = ".openai.azure.com" + return host.replace(suffix, "") if host.endswith(suffix) else None + + +DEFAULT_AZURE_API_VERSION = "2023-05-15" + + +class AzureOpenAIAugmentedLLM(OpenAIAugmentedLLM): + """ + Azure OpenAI implementation extending OpenAIAugmentedLLM. + Handles both API Key and DefaultAzureCredential authentication. + """ + + def __init__(self, provider: Provider = Provider.AZURE, *args, **kwargs): + # Set provider to AZURE, pass through to base + super().__init__(provider=provider, *args, **kwargs) + + # Context/config extraction + context = getattr(self, "context", None) + config = getattr(context, "config", None) if context else None + azure_cfg = getattr(config, "azure", None) if config else None + + if azure_cfg is None: + raise ProviderKeyError( + "Missing Azure configuration", + "Azure provider requires configuration section 'azure' in your config file.", + ) + + self.use_default_cred = getattr(azure_cfg, "use_default_azure_credential", False) + default_request_params = getattr(self, "default_request_params", None) + self.deployment_name = getattr(default_request_params, "model", None) or getattr( + azure_cfg, "azure_deployment", None + ) + self.api_version = getattr(azure_cfg, "api_version", None) or DEFAULT_AZURE_API_VERSION + + if self.use_default_cred: + self.base_url = getattr(azure_cfg, "base_url", None) + if not self.base_url: + raise ProviderKeyError( + "Missing Azure endpoint", + "When using 'use_default_azure_credential', 'base_url' is required in azure config.", + ) + if DefaultAzureCredential is None: + raise ProviderKeyError( + "azure-identity not installed", + "You must install 'azure-identity' to use DefaultAzureCredential authentication.", + ) + self.credential = DefaultAzureCredential() + + def get_azure_token(): + token = self.credential.get_token("https://cognitiveservices.azure.com/.default") + return token.token + + self.get_azure_token = get_azure_token + else: + self.api_key = getattr(azure_cfg, "api_key", None) + self.resource_name = getattr(azure_cfg, "resource_name", None) + self.base_url = getattr(azure_cfg, "base_url", None) or ( + f"https://{self.resource_name}.openai.azure.com/" if self.resource_name else None + ) + if not self.api_key: + raise ProviderKeyError( + "Missing Azure OpenAI credentials", + "Field 'api_key' is required in azure config.", + ) + if not (self.resource_name or self.base_url): + raise ProviderKeyError( + "Missing Azure endpoint", + "Provide either 'resource_name' or 'base_url' under azure config.", + ) + if not self.deployment_name: + raise ProviderKeyError( + "Missing deployment name", + "Set 'azure_deployment' in config or pass model=.", + ) + # If resource_name was missing, try to extract it from base_url + if not self.resource_name and self.base_url: + self.resource_name = _extract_resource_name(self.base_url) + + def _openai_client(self) -> OpenAI: + """ + Returns an AzureOpenAI client, handling both API Key and DefaultAzureCredential. + """ + try: + if self.use_default_cred: + if self.base_url is None: + raise ProviderKeyError( + "Missing Azure endpoint", + "azure_endpoint (base_url) is None at client creation time.", + ) + return AzureOpenAI( + azure_ad_token_provider=self.get_azure_token, + azure_endpoint=self.base_url, + api_version=self.api_version, + azure_deployment=self.deployment_name, + ) + else: + if self.base_url is None: + raise ProviderKeyError( + "Missing Azure endpoint", + "azure_endpoint (base_url) is None at client creation time.", + ) + return AzureOpenAI( + api_key=self.api_key, + azure_endpoint=self.base_url, + api_version=self.api_version, + azure_deployment=self.deployment_name, + ) + except AuthenticationError as e: + if self.use_default_cred: + raise ProviderKeyError( + "Invalid Azure AD credentials", + "The configured Azure AD credentials were rejected.\n" + "Please check your Azure identity setup.", + ) from e + else: + raise ProviderKeyError( + "Invalid Azure OpenAI API key", + "The configured Azure OpenAI API key was rejected.\n" + "Please check that your API key is valid and not expired.", + ) from e diff --git a/src/mcp_agent/llm/providers/augmented_llm_openai.py b/src/mcp_agent/llm/providers/augmented_llm_openai.py index 1e876184c..f2047fadf 100644 --- a/src/mcp_agent/llm/providers/augmented_llm_openai.py +++ b/src/mcp_agent/llm/providers/augmented_llm_openai.py @@ -355,7 +355,7 @@ def _prepare_api_request( def adjust_schema(self, inputSchema: Dict) -> Dict: # return inputSchema - if not Provider.OPENAI == self.provider: + if self.provider not in [Provider.OPENAI, Provider.AZURE]: return inputSchema if "properties" in inputSchema: diff --git a/tests/unit/mcp_agent/cli/commands/test_check_config.py b/tests/unit/mcp_agent/cli/commands/test_check_config.py new file mode 100644 index 000000000..adff5c2e6 --- /dev/null +++ b/tests/unit/mcp_agent/cli/commands/test_check_config.py @@ -0,0 +1,69 @@ +from mcp_agent.cli.commands.check_config import API_KEY_HINT_TEXT, check_api_keys + + +def make_secrets_summary(azure_cfg): + return {"status": "parsed", "error": None, "secrets": {"azure": azure_cfg}} + + +def test_check_api_keys_only_api_key(): + azure_cfg = { + "api_key": "test-azure-key", + "resource_name": "test-resource", + "azure_deployment": "test-deployment", + "api_version": "2023-05-15", + } + summary = make_secrets_summary(azure_cfg) + results = check_api_keys(summary, {}) + assert results["azure"]["config"] == "...e-key" + + +def test_check_api_keys_only_default_cred(): + azure_cfg = { + "use_default_azure_credential": True, + "base_url": "https://mydemo.openai.azure.com/", + "azure_deployment": "test-deployment", + "api_version": "2023-05-15", + } + summary = make_secrets_summary(azure_cfg) + results = check_api_keys(summary, {}) + assert results["azure"]["config"] == "DefaultAzureCredential" + + +def test_check_api_keys_both_modes(): + azure_cfg = { + "api_key": "test-azure-key", + "use_default_azure_credential": True, + "base_url": "https://mydemo.openai.azure.com/", + "azure_deployment": "test-deployment", + "api_version": "2023-05-15", + } + summary = make_secrets_summary(azure_cfg) + results = check_api_keys(summary, {}) + assert "DefaultAzureCredential" in results["azure"]["config"] + assert "api_key" in results["azure"]["config"] + + +def test_check_api_keys_invalid_config(): + azure_cfg = { + "use_default_azure_credential": True, + # missing base_url + "azure_deployment": "test-deployment", + "api_version": "2023-05-15", + } + summary = make_secrets_summary(azure_cfg) + results = check_api_keys(summary, {}) + # Should not mark as DefaultAzureCredential if base_url missing + assert results["azure"]["config"] == "" + + +def test_check_api_keys_hint_text(): + azure_cfg = { + "api_key": API_KEY_HINT_TEXT, + "resource_name": "test-resource", + "azure_deployment": "test-deployment", + "api_version": "2023-05-15", + } + summary = make_secrets_summary(azure_cfg) + results = check_api_keys(summary, {}) + # Should not show API_KEY_HINT_TEXT as a valid key + assert results["azure"]["config"] == "" diff --git a/tests/unit/mcp_agent/llm/providers/test_augmented_llm_azure.py b/tests/unit/mcp_agent/llm/providers/test_augmented_llm_azure.py new file mode 100644 index 000000000..7332cf6d0 --- /dev/null +++ b/tests/unit/mcp_agent/llm/providers/test_augmented_llm_azure.py @@ -0,0 +1,99 @@ +import types +from typing import Optional + +import pytest + +from mcp_agent.llm.providers.augmented_llm_azure import AzureOpenAIAugmentedLLM + + +class DummyLogger: + enable_markup = True + + +class DummyAzureConfig: + def __init__(self): + self.api_key: Optional[str] = "test-key" + self.resource_name: Optional[str] = "test-resource" + self.azure_deployment: Optional[str] = "test-deployment" + self.api_version: Optional[str] = "2023-05-15" + self.base_url: Optional[str] = None + self.use_default_azure_credential: bool = False + + +class DummyConfig: + def __init__(self, azure_cfg=None): + self.azure = azure_cfg or DummyAzureConfig() + self.logger = DummyLogger() + self.openai = None # For compatibility with OpenAIAugmentedLLM + + +class DummyContext: + def __init__(self, azure_cfg=None): + self.config = DummyConfig(azure_cfg=azure_cfg) + self.executor = None + + +def test_openai_client_with_base_url_only(): + cfg = DummyAzureConfig() + cfg.base_url = "https://mydemo.openai.azure.com/" + cfg.resource_name = None + ctx = DummyContext(azure_cfg=cfg) + llm = AzureOpenAIAugmentedLLM(context=ctx) + client = llm._openai_client() + assert hasattr(client, "chat") + # Should be AzureOpenAI instance + + +@pytest.mark.asyncio +async def test_openai_client_with_default_azure_credential(monkeypatch): + """ + Test AzureOpenAIAugmentedLLM with use_default_azure_credential: True. + Mocks DefaultAzureCredential and AzureOpenAI to ensure correct integration. + """ + + class DummyToken: + def __init__(self, token): + self.token = token + + class DummyCredential: + def get_token(self, scope): + assert scope == "https://cognitiveservices.azure.com/.default" + return DummyToken("dummy-token") + + import mcp_agent.llm.providers.augmented_llm_azure as azure_mod + + monkeypatch.setattr(azure_mod, "DefaultAzureCredential", DummyCredential) + + class DummyAzureOpenAI: + def __init__(self, **kwargs): + assert "azure_ad_token_provider" in kwargs + self.token_provider = kwargs["azure_ad_token_provider"] + self.chat = types.SimpleNamespace( + completions=types.SimpleNamespace( + create=lambda **kw: types.SimpleNamespace( + choices=[ + types.SimpleNamespace( + message=types.SimpleNamespace(content="tokenpong") + ) + ] + ) + ) + ) + + monkeypatch.setattr(azure_mod, "AzureOpenAI", DummyAzureOpenAI) + + class DACfg: + def __init__(self): + self.api_key = None + self.resource_name = None + self.azure_deployment = "test-deployment" + self.api_version = "2023-05-15" + self.base_url = "https://mydemo.openai.azure.com/" + self.use_default_azure_credential = True + + dacfg = DACfg() + ctx = DummyContext(azure_cfg=dacfg) + llm = AzureOpenAIAugmentedLLM(context=ctx) + client = llm._openai_client() + # Just checking that the client is created and has chat + assert hasattr(client, "chat") diff --git a/uv.lock b/uv.lock index e41640249..e9bdb7055 100644 --- a/uv.lock +++ b/uv.lock @@ -190,6 +190,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, ] +[[package]] +name = "azure-core" +version = "1.34.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, + { name = "six" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/29/ff7a519a315e41c85bab92a7478c6acd1cf0b14353139a08caee4c691f77/azure_core-1.34.0.tar.gz", hash = "sha256:bdb544989f246a0ad1c85d72eeb45f2f835afdcbc5b45e43f0dbde7461c81ece", size = 297999 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/9e/5c87b49f65bb16571599bc789857d0ded2f53014d3392bc88a5d1f3ad779/azure_core-1.34.0-py3-none-any.whl", hash = "sha256:0615d3b756beccdb6624d1c0ae97284f38b78fb59a2a9839bf927c66fbbdddd6", size = 207409 }, +] + +[[package]] +name = "azure-identity" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "cryptography" }, + { name = "msal" }, + { name = "msal-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/8e/1b5916f5e1696bf05b009cf7d41383cea54aa8536d4a4f6f88cca15eb6a4/azure_identity-1.22.0.tar.gz", hash = "sha256:c8f5ef23e5295c2fa300c984dd9f5e1fe43503fc25c121c37ff6a15e39b800b9", size = 263346 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/1a/6f13d7f95f68f37303c0e00e011d498e4524e70d354b2e11ef5ae89e0ce0/azure_identity-1.22.0-py3-none-any.whl", hash = "sha256:26d6c63f2ca453c77c3e74be8613941ad074e05d0c8be135247573752c249ad8", size = 185524 }, +] + [[package]] name = "certifi" version = "2025.4.26" @@ -199,6 +229,63 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3", size = 159618 }, ] +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/07/f44ca684db4e4f08a3fdc6eeb9a0d15dc6883efc7b8c90357fdbf74e186c/cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14", size = 182191 }, + { url = "https://files.pythonhosted.org/packages/08/fd/cc2fedbd887223f9f5d170c96e57cbf655df9831a6546c1727ae13fa977a/cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67", size = 178592 }, + { url = "https://files.pythonhosted.org/packages/de/cc/4635c320081c78d6ffc2cab0a76025b691a91204f4aa317d568ff9280a2d/cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382", size = 426024 }, + { url = "https://files.pythonhosted.org/packages/b6/7b/3b2b250f3aab91abe5f8a51ada1b717935fdaec53f790ad4100fe2ec64d1/cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702", size = 448188 }, + { url = "https://files.pythonhosted.org/packages/d3/48/1b9283ebbf0ec065148d8de05d647a986c5f22586b18120020452fff8f5d/cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3", size = 455571 }, + { url = "https://files.pythonhosted.org/packages/40/87/3b8452525437b40f39ca7ff70276679772ee7e8b394934ff60e63b7b090c/cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6", size = 436687 }, + { url = "https://files.pythonhosted.org/packages/8d/fb/4da72871d177d63649ac449aec2e8a29efe0274035880c7af59101ca2232/cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17", size = 446211 }, + { url = "https://files.pythonhosted.org/packages/ab/a0/62f00bcb411332106c02b663b26f3545a9ef136f80d5df746c05878f8c4b/cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8", size = 461325 }, + { url = "https://files.pythonhosted.org/packages/36/83/76127035ed2e7e27b0787604d99da630ac3123bfb02d8e80c633f218a11d/cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e", size = 438784 }, + { url = "https://files.pythonhosted.org/packages/21/81/a6cd025db2f08ac88b901b745c163d884641909641f9b826e8cb87645942/cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be", size = 461564 }, + { url = "https://files.pythonhosted.org/packages/f8/fe/4d41c2f200c4a457933dbd98d3cf4e911870877bd94d9656cc0fcb390681/cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c", size = 171804 }, + { url = "https://files.pythonhosted.org/packages/d1/b6/0b0f5ab93b0df4acc49cae758c81fe4e5ef26c3ae2e10cc69249dfd8b3ab/cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15", size = 181299 }, + { url = "https://files.pythonhosted.org/packages/6b/f4/927e3a8899e52a27fa57a48607ff7dc91a9ebe97399b357b85a0c7892e00/cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401", size = 182264 }, + { url = "https://files.pythonhosted.org/packages/6c/f5/6c3a8efe5f503175aaddcbea6ad0d2c96dad6f5abb205750d1b3df44ef29/cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf", size = 178651 }, + { url = "https://files.pythonhosted.org/packages/94/dd/a3f0118e688d1b1a57553da23b16bdade96d2f9bcda4d32e7d2838047ff7/cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4", size = 445259 }, + { url = "https://files.pythonhosted.org/packages/2e/ea/70ce63780f096e16ce8588efe039d3c4f91deb1dc01e9c73a287939c79a6/cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41", size = 469200 }, + { url = "https://files.pythonhosted.org/packages/1c/a0/a4fa9f4f781bda074c3ddd57a572b060fa0df7655d2a4247bbe277200146/cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1", size = 477235 }, + { url = "https://files.pythonhosted.org/packages/62/12/ce8710b5b8affbcdd5c6e367217c242524ad17a02fe5beec3ee339f69f85/cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6", size = 459721 }, + { url = "https://files.pythonhosted.org/packages/ff/6b/d45873c5e0242196f042d555526f92aa9e0c32355a1be1ff8c27f077fd37/cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d", size = 467242 }, + { url = "https://files.pythonhosted.org/packages/1a/52/d9a0e523a572fbccf2955f5abe883cfa8bcc570d7faeee06336fbd50c9fc/cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6", size = 477999 }, + { url = "https://files.pythonhosted.org/packages/44/74/f2a2460684a1a2d00ca799ad880d54652841a780c4c97b87754f660c7603/cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f", size = 454242 }, + { url = "https://files.pythonhosted.org/packages/f8/4a/34599cac7dfcd888ff54e801afe06a19c17787dfd94495ab0c8d35fe99fb/cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b", size = 478604 }, + { url = "https://files.pythonhosted.org/packages/34/33/e1b8a1ba29025adbdcda5fb3a36f94c03d771c1b7b12f726ff7fef2ebe36/cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655", size = 171727 }, + { url = "https://files.pythonhosted.org/packages/3d/97/50228be003bb2802627d28ec0627837ac0bf35c90cf769812056f235b2d1/cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0", size = 181400 }, + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + [[package]] name = "cfgv" version = "3.4.0" @@ -355,6 +442,53 @@ toml = [ { name = "tomli", marker = "python_full_version <= '3.11'" }, ] +[[package]] +name = "cryptography" +version = "44.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/d6/1411ab4d6108ab167d06254c5be517681f1e331f90edf1379895bcb87020/cryptography-44.0.3.tar.gz", hash = "sha256:fe19d8bc5536a91a24a8133328880a41831b6c5df54599a8417b62fe015d3053", size = 711096 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/53/c776d80e9d26441bb3868457909b4e74dd9ccabd182e10b2b0ae7a07e265/cryptography-44.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:962bc30480a08d133e631e8dfd4783ab71cc9e33d5d7c1e192f0b7c06397bb88", size = 6670281 }, + { url = "https://files.pythonhosted.org/packages/6a/06/af2cf8d56ef87c77319e9086601bef621bedf40f6f59069e1b6d1ec498c5/cryptography-44.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffc61e8f3bf5b60346d89cd3d37231019c17a081208dfbbd6e1605ba03fa137", size = 3959305 }, + { url = "https://files.pythonhosted.org/packages/ae/01/80de3bec64627207d030f47bf3536889efee8913cd363e78ca9a09b13c8e/cryptography-44.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58968d331425a6f9eedcee087f77fd3c927c88f55368f43ff7e0a19891f2642c", size = 4171040 }, + { url = "https://files.pythonhosted.org/packages/bd/48/bb16b7541d207a19d9ae8b541c70037a05e473ddc72ccb1386524d4f023c/cryptography-44.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e28d62e59a4dbd1d22e747f57d4f00c459af22181f0b2f787ea83f5a876d7c76", size = 3963411 }, + { url = "https://files.pythonhosted.org/packages/42/b2/7d31f2af5591d217d71d37d044ef5412945a8a8e98d5a2a8ae4fd9cd4489/cryptography-44.0.3-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af653022a0c25ef2e3ffb2c673a50e5a0d02fecc41608f4954176f1933b12359", size = 3689263 }, + { url = "https://files.pythonhosted.org/packages/25/50/c0dfb9d87ae88ccc01aad8eb93e23cfbcea6a6a106a9b63a7b14c1f93c75/cryptography-44.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:157f1f3b8d941c2bd8f3ffee0af9b049c9665c39d3da9db2dc338feca5e98a43", size = 4196198 }, + { url = "https://files.pythonhosted.org/packages/66/c9/55c6b8794a74da652690c898cb43906310a3e4e4f6ee0b5f8b3b3e70c441/cryptography-44.0.3-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:c6cd67722619e4d55fdb42ead64ed8843d64638e9c07f4011163e46bc512cf01", size = 3966502 }, + { url = "https://files.pythonhosted.org/packages/b6/f7/7cb5488c682ca59a02a32ec5f975074084db4c983f849d47b7b67cc8697a/cryptography-44.0.3-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:b424563394c369a804ecbee9b06dfb34997f19d00b3518e39f83a5642618397d", size = 4196173 }, + { url = "https://files.pythonhosted.org/packages/d2/0b/2f789a8403ae089b0b121f8f54f4a3e5228df756e2146efdf4a09a3d5083/cryptography-44.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c91fc8e8fd78af553f98bc7f2a1d8db977334e4eea302a4bfd75b9461c2d8904", size = 4087713 }, + { url = "https://files.pythonhosted.org/packages/1d/aa/330c13655f1af398fc154089295cf259252f0ba5df93b4bc9d9c7d7f843e/cryptography-44.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:25cd194c39fa5a0aa4169125ee27d1172097857b27109a45fadc59653ec06f44", size = 4299064 }, + { url = "https://files.pythonhosted.org/packages/10/a8/8c540a421b44fd267a7d58a1fd5f072a552d72204a3f08194f98889de76d/cryptography-44.0.3-cp37-abi3-win32.whl", hash = "sha256:3be3f649d91cb182c3a6bd336de8b61a0a71965bd13d1a04a0e15b39c3d5809d", size = 2773887 }, + { url = "https://files.pythonhosted.org/packages/b9/0d/c4b1657c39ead18d76bbd122da86bd95bdc4095413460d09544000a17d56/cryptography-44.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:3883076d5c4cc56dbef0b898a74eb6992fdac29a7b9013870b34efe4ddb39a0d", size = 3209737 }, + { url = "https://files.pythonhosted.org/packages/34/a3/ad08e0bcc34ad436013458d7528e83ac29910943cea42ad7dd4141a27bbb/cryptography-44.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:5639c2b16764c6f76eedf722dbad9a0914960d3489c0cc38694ddf9464f1bb2f", size = 6673501 }, + { url = "https://files.pythonhosted.org/packages/b1/f0/7491d44bba8d28b464a5bc8cc709f25a51e3eac54c0a4444cf2473a57c37/cryptography-44.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ffef566ac88f75967d7abd852ed5f182da252d23fac11b4766da3957766759", size = 3960307 }, + { url = "https://files.pythonhosted.org/packages/f7/c8/e5c5d0e1364d3346a5747cdcd7ecbb23ca87e6dea4f942a44e88be349f06/cryptography-44.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:192ed30fac1728f7587c6f4613c29c584abdc565d7417c13904708db10206645", size = 4170876 }, + { url = "https://files.pythonhosted.org/packages/73/96/025cb26fc351d8c7d3a1c44e20cf9a01e9f7cf740353c9c7a17072e4b264/cryptography-44.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7d5fe7195c27c32a64955740b949070f21cba664604291c298518d2e255931d2", size = 3964127 }, + { url = "https://files.pythonhosted.org/packages/01/44/eb6522db7d9f84e8833ba3bf63313f8e257729cf3a8917379473fcfd6601/cryptography-44.0.3-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3f07943aa4d7dad689e3bb1638ddc4944cc5e0921e3c227486daae0e31a05e54", size = 3689164 }, + { url = "https://files.pythonhosted.org/packages/68/fb/d61a4defd0d6cee20b1b8a1ea8f5e25007e26aeb413ca53835f0cae2bcd1/cryptography-44.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb90f60e03d563ca2445099edf605c16ed1d5b15182d21831f58460c48bffb93", size = 4198081 }, + { url = "https://files.pythonhosted.org/packages/1b/50/457f6911d36432a8811c3ab8bd5a6090e8d18ce655c22820994913dd06ea/cryptography-44.0.3-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ab0b005721cc0039e885ac3503825661bd9810b15d4f374e473f8c89b7d5460c", size = 3967716 }, + { url = "https://files.pythonhosted.org/packages/35/6e/dca39d553075980ccb631955c47b93d87d27f3596da8d48b1ae81463d915/cryptography-44.0.3-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:3bb0847e6363c037df8f6ede57d88eaf3410ca2267fb12275370a76f85786a6f", size = 4197398 }, + { url = "https://files.pythonhosted.org/packages/9b/9d/d1f2fe681eabc682067c66a74addd46c887ebacf39038ba01f8860338d3d/cryptography-44.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0cc66c74c797e1db750aaa842ad5b8b78e14805a9b5d1348dc603612d3e3ff5", size = 4087900 }, + { url = "https://files.pythonhosted.org/packages/c4/f5/3599e48c5464580b73b236aafb20973b953cd2e7b44c7c2533de1d888446/cryptography-44.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6866df152b581f9429020320e5eb9794c8780e90f7ccb021940d7f50ee00ae0b", size = 4301067 }, + { url = "https://files.pythonhosted.org/packages/a7/6c/d2c48c8137eb39d0c193274db5c04a75dab20d2f7c3f81a7dcc3a8897701/cryptography-44.0.3-cp39-abi3-win32.whl", hash = "sha256:c138abae3a12a94c75c10499f1cbae81294a6f983b3af066390adee73f433028", size = 2775467 }, + { url = "https://files.pythonhosted.org/packages/c9/ad/51f212198681ea7b0deaaf8846ee10af99fba4e894f67b353524eab2bbe5/cryptography-44.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:5d186f32e52e66994dce4f766884bcb9c68b8da62d61d9d215bfe5fb56d21334", size = 3210375 }, + { url = "https://files.pythonhosted.org/packages/7f/10/abcf7418536df1eaba70e2cfc5c8a0ab07aa7aa02a5cbc6a78b9d8b4f121/cryptography-44.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cad399780053fb383dc067475135e41c9fe7d901a97dd5d9c5dfb5611afc0d7d", size = 3393192 }, + { url = "https://files.pythonhosted.org/packages/06/59/ecb3ef380f5891978f92a7f9120e2852b1df6f0a849c277b8ea45b865db2/cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:21a83f6f35b9cc656d71b5de8d519f566df01e660ac2578805ab245ffd8523f8", size = 3898419 }, + { url = "https://files.pythonhosted.org/packages/bb/d0/35e2313dbb38cf793aa242182ad5bc5ef5c8fd4e5dbdc380b936c7d51169/cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fc3c9babc1e1faefd62704bb46a69f359a9819eb0292e40df3fb6e3574715cd4", size = 4117892 }, + { url = "https://files.pythonhosted.org/packages/dc/c8/31fb6e33b56c2c2100d76de3fd820afaa9d4d0b6aea1ccaf9aaf35dc7ce3/cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:e909df4053064a97f1e6565153ff8bb389af12c5c8d29c343308760890560aff", size = 3900855 }, + { url = "https://files.pythonhosted.org/packages/43/2a/08cc2ec19e77f2a3cfa2337b429676406d4bb78ddd130a05c458e7b91d73/cryptography-44.0.3-pp310-pypy310_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:dad80b45c22e05b259e33ddd458e9e2ba099c86ccf4e88db7bbab4b747b18d06", size = 4117619 }, + { url = "https://files.pythonhosted.org/packages/02/68/fc3d3f84022a75f2ac4b1a1c0e5d6a0c2ea259e14cd4aae3e0e68e56483c/cryptography-44.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:479d92908277bed6e1a1c69b277734a7771c2b78633c224445b5c60a9f4bc1d9", size = 3136570 }, + { url = "https://files.pythonhosted.org/packages/8d/4b/c11ad0b6c061902de5223892d680e89c06c7c4d606305eb8de56c5427ae6/cryptography-44.0.3-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:896530bc9107b226f265effa7ef3f21270f18a2026bc09fed1ebd7b66ddf6375", size = 3390230 }, + { url = "https://files.pythonhosted.org/packages/58/11/0a6bf45d53b9b2290ea3cec30e78b78e6ca29dc101e2e296872a0ffe1335/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9b4d4a5dbee05a2c390bf212e78b99434efec37b17a4bff42f50285c5c8c9647", size = 3895216 }, + { url = "https://files.pythonhosted.org/packages/0a/27/b28cdeb7270e957f0077a2c2bfad1b38f72f1f6d699679f97b816ca33642/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02f55fb4f8b79c1221b0961488eaae21015b69b210e18c386b69de182ebb1259", size = 4115044 }, + { url = "https://files.pythonhosted.org/packages/35/b0/ec4082d3793f03cb248881fecefc26015813199b88f33e3e990a43f79835/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:dd3db61b8fe5be220eee484a17233287d0be6932d056cf5738225b9c05ef4fff", size = 3898034 }, + { url = "https://files.pythonhosted.org/packages/0b/7f/adf62e0b8e8d04d50c9a91282a57628c00c54d4ae75e2b02a223bd1f2613/cryptography-44.0.3-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:978631ec51a6bbc0b7e58f23b68a8ce9e5f09721940933e9c217068388789fe5", size = 4114449 }, + { url = "https://files.pythonhosted.org/packages/87/62/d69eb4a8ee231f4bf733a92caf9da13f1c81a44e874b1d4080c25ecbb723/cryptography-44.0.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:5d20cc348cca3a8aa7312f42ab953a56e15323800ca3ab0706b8cd452a3a056c", size = 3134369 }, +] + [[package]] name = "decorator" version = "5.2.1" @@ -420,14 +554,15 @@ dependencies = [ { name = "a2a-types" }, { name = "aiohttp" }, { name = "anthropic" }, + { name = "azure-identity" }, { name = "fastapi" }, { name = "mcp" }, { name = "openai" }, { name = "opentelemetry-distro" }, { name = "opentelemetry-exporter-otlp-proto-http" }, - { name = "opentelemetry-instrumentation-anthropic" }, + { name = "opentelemetry-instrumentation-anthropic", marker = "python_full_version < '4.0'" }, { name = "opentelemetry-instrumentation-mcp" }, - { name = "opentelemetry-instrumentation-openai" }, + { name = "opentelemetry-instrumentation-openai", marker = "python_full_version < '4.0'" }, { name = "prompt-toolkit" }, { name = "pydantic" }, { name = "pydantic-settings" }, @@ -438,6 +573,9 @@ dependencies = [ ] [package.optional-dependencies] +azure = [ + { name = "azure-identity" }, +] dev = [ { name = "anthropic" }, { name = "pre-commit" }, @@ -473,15 +611,17 @@ requires-dist = [ { name = "aiohttp", specifier = ">=3.11.13" }, { name = "anthropic", specifier = ">=0.49.0" }, { name = "anthropic", marker = "extra == 'dev'", specifier = ">=0.42.0" }, + { name = "azure-identity", specifier = ">=1.14.0" }, + { name = "azure-identity", marker = "extra == 'azure'", specifier = ">=1.14.0" }, { name = "fastapi", specifier = ">=0.115.6" }, { name = "mcp", specifier = ">=1.8.0" }, { name = "openai", specifier = ">=1.63.2" }, { name = "openai", marker = "extra == 'openai'", specifier = ">=1.58.1" }, { name = "opentelemetry-distro", specifier = ">=0.50b0" }, { name = "opentelemetry-exporter-otlp-proto-http", specifier = ">=1.29.0" }, - { name = "opentelemetry-instrumentation-anthropic", specifier = ">=0.39.3" }, + { name = "opentelemetry-instrumentation-anthropic", marker = "python_full_version >= '3.10' and python_full_version < '4.0'", specifier = ">=0.39.3" }, { name = "opentelemetry-instrumentation-mcp", specifier = ">=0.40.3" }, - { name = "opentelemetry-instrumentation-openai", specifier = ">=0.39.3" }, + { name = "opentelemetry-instrumentation-openai", marker = "python_full_version >= '3.10' and python_full_version < '4.0'", specifier = ">=0.39.3" }, { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=4.0.1" }, { name = "prompt-toolkit", specifier = ">=3.0.50" }, { name = "pydantic", specifier = ">=2.10.4" }, @@ -498,7 +638,7 @@ requires-dist = [ { name = "tomli", marker = "extra == 'dev'", specifier = ">=2.2.1" }, { name = "typer", specifier = ">=0.15.1" }, ] -provides-extras = ["openai", "dev"] +provides-extras = ["openai", "azure", "dev"] [package.metadata.requires-dev] dev = [ @@ -929,6 +1069,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, ] +[[package]] +name = "msal" +version = "1.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/90/81dcc50f0be11a8c4dcbae1a9f761a26e5f905231330a7cacc9f04ec4c61/msal-1.32.3.tar.gz", hash = "sha256:5eea038689c78a5a70ca8ecbe1245458b55a857bd096efb6989c69ba15985d35", size = 151449 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/bf/81516b9aac7fd867709984d08eb4db1d2e3fe1df795c8e442cde9b568962/msal-1.32.3-py3-none-any.whl", hash = "sha256:b2798db57760b1961b142f027ffb7c8169536bf77316e99a0df5c4aaebb11569", size = 115358 }, +] + +[[package]] +name = "msal-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583 }, +] + [[package]] name = "multidict" version = "6.4.3" @@ -1417,6 +1583,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, ] +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + [[package]] name = "pydantic" version = "2.11.4" @@ -1542,6 +1717,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 }, ] +[[package]] +name = "pyjwt" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + [[package]] name = "pytest" version = "8.3.5" @@ -1778,6 +1967,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, ] +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, +] + [[package]] name = "sniffio" version = "1.3.1"