This is an automated email from the ASF dual-hosted git repository.

shahar1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new c860159eee8 Add Azure AI Foundry Agents operators (Create, Update, 
Delete, Run) (#68799)
c860159eee8 is described below

commit c860159eee8c9e86670e1418a90f530719419093
Author: Morgan <[email protected]>
AuthorDate: Sat Jul 11 04:18:01 2026 -0300

    Add Azure AI Foundry Agents operators (Create, Update, Delete, Run) (#68799)
---
 docs/spelling_wordlist.txt                         |   4 +
 providers/microsoft/azure/README.rst               |   2 +
 providers/microsoft/azure/docs/index.rst           |   2 +
 .../microsoft/azure/docs/operators/ai_agents.rst   | 128 ++++++
 providers/microsoft/azure/provider.yaml            |  52 +++
 providers/microsoft/azure/pyproject.toml           |   3 +
 .../providers/microsoft/azure/_ai_agents.py        |  72 ++++
 .../providers/microsoft/azure/get_provider_info.py |  56 +++
 .../providers/microsoft/azure/hooks/ai_agents.py   | 454 ++++++++++++++++++++
 .../microsoft/azure/operators/ai_agents.py         | 398 +++++++++++++++++
 .../microsoft/azure/triggers/ai_agents.py          | 175 ++++++++
 .../microsoft/azure/example_azure_ai_agents.py     | 230 ++++++++++
 .../unit/microsoft/azure/hooks/test_ai_agents.py   | 475 +++++++++++++++++++++
 .../microsoft/azure/operators/test_ai_agents.py    | 463 ++++++++++++++++++++
 .../tests/unit/microsoft/azure/test__ai_agents.py  |  77 ++++
 .../microsoft/azure/triggers/test_ai_agents.py     | 197 +++++++++
 uv.lock                                            |  21 +
 17 files changed, 2809 insertions(+)

diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt
index 2b7c13ee660..728ab391dc7 100644
--- a/docs/spelling_wordlist.txt
+++ b/docs/spelling_wordlist.txt
@@ -24,7 +24,9 @@ AdsInsights
 adsinsights
 afterall
 agentcore
+AgentDetails
 agentic
+AgentVersionDetails
 ai
 aio
 aiobotocore
@@ -449,6 +451,8 @@ deferrable
 deidentify
 DeidentifyTemplate
 del
+DeleteAgentResponse
+DeleteAgentVersionResponse
 delim
 deliverability
 deltalake
diff --git a/providers/microsoft/azure/README.rst 
b/providers/microsoft/azure/README.rst
index 450b5b11bd1..63f0e97e181 100644
--- a/providers/microsoft/azure/README.rst
+++ b/providers/microsoft/azure/README.rst
@@ -56,7 +56,9 @@ PIP package                                 Version required
 ``apache-airflow``                          ``>=2.11.0``
 ``apache-airflow-providers-common-compat``  ``>=1.13.0``
 ``adlfs``                                   ``>=2023.10.0``
+``aiohttp``                                 ``>=3.14.0``
 ``azure-batch``                             ``<15.0.0,>=8.0.0``
+``azure-ai-projects``                       ``>=2.2.0``
 ``azure-cosmos``                            ``>=4.6.0``
 ``azure-mgmt-cosmosdb``                     ``>=3.0.0``
 ``azure-datalake-store``                    ``>=0.0.45``
diff --git a/providers/microsoft/azure/docs/index.rst 
b/providers/microsoft/azure/docs/index.rst
index fd9442fa473..0ad9ca9512f 100644
--- a/providers/microsoft/azure/docs/index.rst
+++ b/providers/microsoft/azure/docs/index.rst
@@ -110,7 +110,9 @@ PIP package                                 Version required
 ``apache-airflow``                          ``>=2.11.0``
 ``apache-airflow-providers-common-compat``  ``>=1.13.0``
 ``adlfs``                                   ``>=2023.10.0``
+``aiohttp``                                 ``>=3.14.0``
 ``azure-batch``                             ``<15.0.0,>=8.0.0``
+``azure-ai-projects``                       ``>=2.2.0``
 ``azure-cosmos``                            ``>=4.6.0``
 ``azure-mgmt-cosmosdb``                     ``>=3.0.0``
 ``azure-datalake-store``                    ``>=0.0.45``
diff --git a/providers/microsoft/azure/docs/operators/ai_agents.rst 
b/providers/microsoft/azure/docs/operators/ai_agents.rst
new file mode 100644
index 00000000000..a41cf6ebca4
--- /dev/null
+++ b/providers/microsoft/azure/docs/operators/ai_agents.rst
@@ -0,0 +1,128 @@
+ .. Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+ ..   http://www.apache.org/licenses/LICENSE-2.0
+
+ .. Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+
+Azure AI Foundry Hosted Agents Operators
+========================================
+
+Azure AI Foundry Hosted agents let you run a containerized agent in Microsoft 
Foundry Agent Service.
+Build and push your agent image to Azure Container Registry first, then use 
these operators to create
+agent versions, invoke the hosted endpoint, and clean up the deployment.
+
+Prerequisite Tasks
+^^^^^^^^^^^^^^^^^^
+
+.. include:: /operators/_partials/prerequisite_tasks.rst
+
+The operators use the ``azure_ai_agents_default`` connection by default. 
Configure the Azure AI Foundry
+project endpoint in the connection host field or in the ``endpoint`` 
connection extra. The endpoint
+format is:
+
+.. code-block:: text
+
+    https://<aiservices-id>.services.ai.azure.com/api/projects/<project-name>
+
+The container image must be available in Azure Container Registry and must 
implement one of the
+Hosted agent protocols exposed by Microsoft Foundry, such as ``responses`` or 
``invocations``.
+
+.. _howto/operator:CreateAzureAIAgentOperator:
+
+CreateAzureAIAgentOperator
+--------------------------
+
+To create an Azure AI Hosted agent from a container image, use the
+:class:`~airflow.providers.microsoft.azure.operators.ai_agents.CreateAzureAIAgentOperator`.
+The operator returns the created Hosted agent version as a serializable 
dictionary.
+Optional agent fields such as ``metadata``, ``description``, and 
``blueprint_reference``
+can be passed through to the Foundry API.
+
+.. exampleinclude:: /../tests/system/microsoft/azure/example_azure_ai_agents.py
+    :language: python
+    :dedent: 4
+    :start-after: [START howto_operator_azure_ai_agent_create]
+    :end-before: [END howto_operator_azure_ai_agent_create]
+
+.. _howto/operator:UpdateAzureAIAgentOperator:
+
+UpdateAzureAIAgentOperator
+--------------------------
+
+Azure AI Hosted agent updates are published as new immutable versions. To 
create a new version, use the
+:class:`~airflow.providers.microsoft.azure.operators.ai_agents.UpdateAzureAIAgentOperator`.
+The operator accepts the same parameters as ``CreateAzureAIAgentOperator`` and 
returns the newly
+created Hosted agent version as a serializable dictionary.
+
+.. exampleinclude:: /../tests/system/microsoft/azure/example_azure_ai_agents.py
+    :language: python
+    :dedent: 4
+    :start-after: [START howto_operator_azure_ai_agent_update]
+    :end-before: [END howto_operator_azure_ai_agent_update]
+
+Set ``deferrable=True`` to release the worker slot while the new version 
deploys; the wait then
+runs in the triggerer. The same parameter is available on 
``CreateAzureAIAgentOperator``.
+
+.. exampleinclude:: /../tests/system/microsoft/azure/example_azure_ai_agents.py
+    :language: python
+    :dedent: 4
+    :start-after: [START howto_operator_azure_ai_agent_update_deferrable]
+    :end-before: [END howto_operator_azure_ai_agent_update_deferrable]
+
+.. _howto/operator:RunAzureAIAgentOperator:
+
+RunAzureAIAgentOperator
+-----------------------
+
+To invoke an Azure AI Hosted agent, use the
+:class:`~airflow.providers.microsoft.azure.operators.ai_agents.RunAzureAIAgentOperator`.
+The ``responses`` protocol sends the payload to the agent's OpenAI-compatible 
endpoint, and the
+``invocations`` protocol posts it to the agent's invocations endpoint.
+Pass ``agent_session_id`` to reuse an existing Hosted agent session with the 
``invocations``
+protocol and ``user_isolation_key`` to scope endpoint resources to a specific 
end user.
+The operator returns the JSON-compatible protocol response.
+
+.. exampleinclude:: /../tests/system/microsoft/azure/example_azure_ai_agents.py
+    :language: python
+    :dedent: 8
+    :start-after: [START howto_operator_azure_ai_agent_run]
+    :end-before: [END howto_operator_azure_ai_agent_run]
+
+.. _howto/operator:DeleteAzureAIAgentOperator:
+
+DeleteAzureAIAgentOperator
+--------------------------
+
+To delete an Azure AI Hosted agent and all of its versions, use the
+:class:`~airflow.providers.microsoft.azure.operators.ai_agents.DeleteAzureAIAgentOperator`.
+Pass ``agent_version`` to delete only one version. Set ``force=True`` to also 
delete active sessions
+associated with the agent or version.
+The operator returns the serialized deletion response.
+
+.. exampleinclude:: /../tests/system/microsoft/azure/example_azure_ai_agents.py
+    :language: python
+    :dedent: 4
+    :start-after: [START howto_operator_azure_ai_agent_delete]
+    :end-before: [END howto_operator_azure_ai_agent_delete]
+
+Reference
+---------
+
+For further information, look at:
+
+* `Deploy an Azure AI Hosted agent 
<https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent>`__
+* `Microsoft Foundry Agent Service documentation 
<https://learn.microsoft.com/en-us/azure/foundry/agents/overview>`__
+* `Microsoft Foundry Agents REST API reference 
<https://ai.azure.com/api-reference/agents/>`__
+* `Azure AI Projects Python SDK reference 
<https://learn.microsoft.com/en-us/python/api/azure-ai-projects/azure.ai.projects.aiprojectclient?view=azure-python-preview>`__
diff --git a/providers/microsoft/azure/provider.yaml 
b/providers/microsoft/azure/provider.yaml
index 617242aea77..fe6366023e7 100644
--- a/providers/microsoft/azure/provider.yaml
+++ b/providers/microsoft/azure/provider.yaml
@@ -176,6 +176,12 @@ integrations:
     external-doc-url: https://azure.microsoft.com/
     logo: /docs/integration-logos/Microsoft-Azure.png
     tags: [azure]
+  - integration-name: Microsoft Azure AI Foundry Hosted Agents
+    external-doc-url: https://ai.azure.com/api-reference/agents/
+    how-to-guide:
+      - /docs/apache-airflow-providers-microsoft-azure/operators/ai_agents.rst
+    logo: /docs/integration-logos/Microsoft-Azure.png
+    tags: [azure]
   - integration-name: Microsoft Azure Service Bus
     external-doc-url: https://azure.microsoft.com/en-us/services/service-bus/
     logo: /docs/integration-logos/Service-Bus.svg
@@ -217,6 +223,9 @@ operators:
   - integration-name: Microsoft Azure Batch
     python-modules:
       - airflow.providers.microsoft.azure.operators.batch
+  - integration-name: Microsoft Azure AI Foundry Hosted Agents
+    python-modules:
+      - airflow.providers.microsoft.azure.operators.ai_agents
   - integration-name: Microsoft Azure Container Instances
     python-modules:
       - airflow.providers.microsoft.azure.operators.container_instances
@@ -284,6 +293,9 @@ hooks:
   - integration-name: Microsoft Azure Batch
     python-modules:
       - airflow.providers.microsoft.azure.hooks.batch
+  - integration-name: Microsoft Azure AI Foundry Hosted Agents
+    python-modules:
+      - airflow.providers.microsoft.azure.hooks.ai_agents
   - integration-name: Microsoft Azure Data Lake Storage
     python-modules:
       - airflow.providers.microsoft.azure.hooks.data_lake
@@ -316,6 +328,9 @@ triggers:
   - integration-name: Microsoft Azure Batch
     python-modules:
       - airflow.providers.microsoft.azure.triggers.batch
+  - integration-name: Microsoft Azure AI Foundry Hosted Agents
+    python-modules:
+      - airflow.providers.microsoft.azure.triggers.ai_agents
   - integration-name: Microsoft Azure Compute
     python-modules:
       - airflow.providers.microsoft.azure.triggers.compute
@@ -402,6 +417,43 @@ connection-types:
         label: Workload Identity Tenant ID
         schema:
           type: ["string", "null"]
+  - hook-class-name: 
airflow.providers.microsoft.azure.hooks.ai_agents.AzureAIAgentsHook
+    hook-name: "Azure AI Foundry Hosted Agents"
+    connection-type: azure_ai_agents
+    ui-field-behaviour:
+      hidden-fields: ["schema", "port"]
+      relabeling:
+        host: Project Endpoint
+        login: Azure Client ID
+        password: Azure Secret
+      placeholders:
+        host: 
https://<aiservices-id>.services.ai.azure.com/api/projects/<project-name>
+        login: client_id (token credentials auth)
+        password: secret (token credentials auth)
+        tenantId: tenantId (token credentials auth)
+        cloud_environment: AzurePublicCloud (default) | AzureUSGovernment | 
AzureChinaCloud
+        endpoint: Overrides Project Endpoint from host
+    conn-fields:
+      tenantId:
+        label: Azure Tenant ID
+        schema:
+          type: ["string", "null"]
+      cloud_environment:
+        label: Azure Cloud Environment
+        schema:
+          type: ["string", "null"]
+      endpoint:
+        label: Project Endpoint
+        schema:
+          type: ["string", "null"]
+      managed_identity_client_id:
+        label: Managed Identity Client ID
+        schema:
+          type: ["string", "null"]
+      workload_identity_tenant_id:
+        label: Workload Identity Tenant ID
+        schema:
+          type: ["string", "null"]
   - hook-class-name: 
airflow.providers.microsoft.azure.hooks.compute.AzureComputeHook
     hook-name: "Azure Compute"
     connection-type: azure_compute
diff --git a/providers/microsoft/azure/pyproject.toml 
b/providers/microsoft/azure/pyproject.toml
index 10a3c5030fd..9b2d117593b 100644
--- a/providers/microsoft/azure/pyproject.toml
+++ b/providers/microsoft/azure/pyproject.toml
@@ -62,9 +62,12 @@ dependencies = [
     "apache-airflow>=2.11.0",
     "apache-airflow-providers-common-compat>=1.13.0",
     "adlfs>=2023.10.0",
+    "aiohttp>=3.14.0",
     # azure-batch 15.x is a full rewrite of the Azure SDK (track 2) that 
removes BatchServiceClient, batch_auth,
     # and the other references in AzureBatchHook. Lifting the upper bound cap 
needs a full hook rewrite.
     "azure-batch>=8.0.0,<15.0.0",
+    # 2.2.0 adds force deletion support for Hosted agents and agent versions.
+    "azure-ai-projects>=2.2.0",
     "azure-cosmos>=4.6.0",
     "azure-mgmt-cosmosdb>=3.0.0",
     "azure-datalake-store>=0.0.45",
diff --git 
a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/_ai_agents.py 
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/_ai_agents.py
new file mode 100644
index 00000000000..1936010f9b3
--- /dev/null
+++ 
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/_ai_agents.py
@@ -0,0 +1,72 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+
+if TYPE_CHECKING:
+    from pydantic import JsonValue
+
+VERSION_INTERMEDIATE_STATUSES = {"creating", "deleting"}
+VERSION_SUCCESS_STATUSES = {"active"}
+VERSION_FAILURE_STATUSES = {"failed"}
+VERSION_DELETED_STATUS = "deleted"
+
+
+def _serialize_resource(resource: Any) -> JsonValue:
+    """Serialize an SDK model or response object into XCom-safe primitives."""
+    if resource is None or isinstance(resource, str | int | float | bool):
+        return resource
+    if isinstance(resource, list | tuple):
+        return [_serialize_resource(item) for item in resource]
+    if isinstance(resource, dict):
+        if not all(isinstance(key, str) for key in resource):
+            raise TypeError("Azure AI Hosted agent resources must use string 
keys for XCom serialization.")
+        return {key: _serialize_resource(value) for key, value in 
resource.items()}
+    if hasattr(resource, "as_dict"):
+        return _serialize_resource(resource.as_dict())
+    if hasattr(resource, "model_dump"):
+        return _serialize_resource(resource.model_dump())
+    raise TypeError(
+        f"Cannot serialize Azure AI Hosted agent resource of type 
{type(resource).__name__} for XCom."
+    )
+
+
+def _get_resource_attr(resource: Any, attr: str) -> Any:
+    """Get an attribute from an SDK resource or mapping."""
+    if isinstance(resource, dict):
+        return resource.get(attr)
+    return getattr(resource, attr, None)
+
+
+def _get_version_status(version: Any) -> str:
+    """Return a normalized Hosted agent version status string."""
+    status = _get_resource_attr(version, "status")
+    if hasattr(status, "value"):
+        status = status.value
+    if status is None:
+        raise ValueError("Azure AI Hosted agent version did not include a 
status.")
+    return str(status).lower()
+
+
+def _get_agent_version(version: Any) -> str:
+    """Return the version identifier from a Hosted agent version payload."""
+    agent_version = _get_resource_attr(version, "version")
+    if agent_version is None:
+        raise ValueError("Azure AI Hosted agent response did not include a 
version.")
+    return str(agent_version)
diff --git 
a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/get_provider_info.py
 
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/get_provider_info.py
index ee2e388980f..e6a6be7c87b 100644
--- 
a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/get_provider_info.py
+++ 
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/get_provider_info.py
@@ -103,6 +103,13 @@ def get_provider_info():
                 "logo": "/docs/integration-logos/Microsoft-Azure.png",
                 "tags": ["azure"],
             },
+            {
+                "integration-name": "Microsoft Azure AI Foundry Hosted Agents",
+                "external-doc-url": 
"https://ai.azure.com/api-reference/agents/";,
+                "how-to-guide": 
["/docs/apache-airflow-providers-microsoft-azure/operators/ai_agents.rst"],
+                "logo": "/docs/integration-logos/Microsoft-Azure.png",
+                "tags": ["azure"],
+            },
             {
                 "integration-name": "Microsoft Azure Service Bus",
                 "external-doc-url": 
"https://azure.microsoft.com/en-us/services/service-bus/";,
@@ -158,6 +165,10 @@ def get_provider_info():
                 "integration-name": "Microsoft Azure Batch",
                 "python-modules": 
["airflow.providers.microsoft.azure.operators.batch"],
             },
+            {
+                "integration-name": "Microsoft Azure AI Foundry Hosted Agents",
+                "python-modules": 
["airflow.providers.microsoft.azure.operators.ai_agents"],
+            },
             {
                 "integration-name": "Microsoft Azure Container Instances",
                 "python-modules": 
["airflow.providers.microsoft.azure.operators.container_instances"],
@@ -246,6 +257,10 @@ def get_provider_info():
                 "integration-name": "Microsoft Azure Batch",
                 "python-modules": 
["airflow.providers.microsoft.azure.hooks.batch"],
             },
+            {
+                "integration-name": "Microsoft Azure AI Foundry Hosted Agents",
+                "python-modules": 
["airflow.providers.microsoft.azure.hooks.ai_agents"],
+            },
             {
                 "integration-name": "Microsoft Azure Data Lake Storage",
                 "python-modules": 
["airflow.providers.microsoft.azure.hooks.data_lake"],
@@ -288,6 +303,10 @@ def get_provider_info():
                 "integration-name": "Microsoft Azure Batch",
                 "python-modules": 
["airflow.providers.microsoft.azure.triggers.batch"],
             },
+            {
+                "integration-name": "Microsoft Azure AI Foundry Hosted Agents",
+                "python-modules": 
["airflow.providers.microsoft.azure.triggers.ai_agents"],
+            },
             {
                 "integration-name": "Microsoft Azure Compute",
                 "python-modules": 
["airflow.providers.microsoft.azure.triggers.compute"],
@@ -391,6 +410,43 @@ def get_provider_info():
                     },
                 },
             },
+            {
+                "hook-class-name": 
"airflow.providers.microsoft.azure.hooks.ai_agents.AzureAIAgentsHook",
+                "hook-name": "Azure AI Foundry Hosted Agents",
+                "connection-type": "azure_ai_agents",
+                "ui-field-behaviour": {
+                    "hidden-fields": ["schema", "port"],
+                    "relabeling": {
+                        "host": "Project Endpoint",
+                        "login": "Azure Client ID",
+                        "password": "Azure Secret",
+                    },
+                    "placeholders": {
+                        "host": 
"https://<aiservices-id>.services.ai.azure.com/api/projects/<project-name>",
+                        "login": "client_id (token credentials auth)",
+                        "password": "secret (token credentials auth)",
+                        "tenantId": "tenantId (token credentials auth)",
+                        "cloud_environment": "AzurePublicCloud (default) | 
AzureUSGovernment | AzureChinaCloud",
+                        "endpoint": "Overrides Project Endpoint from host",
+                    },
+                },
+                "conn-fields": {
+                    "tenantId": {"label": "Azure Tenant ID", "schema": 
{"type": ["string", "null"]}},
+                    "cloud_environment": {
+                        "label": "Azure Cloud Environment",
+                        "schema": {"type": ["string", "null"]},
+                    },
+                    "endpoint": {"label": "Project Endpoint", "schema": 
{"type": ["string", "null"]}},
+                    "managed_identity_client_id": {
+                        "label": "Managed Identity Client ID",
+                        "schema": {"type": ["string", "null"]},
+                    },
+                    "workload_identity_tenant_id": {
+                        "label": "Workload Identity Tenant ID",
+                        "schema": {"type": ["string", "null"]},
+                    },
+                },
+            },
             {
                 "hook-class-name": 
"airflow.providers.microsoft.azure.hooks.compute.AzureComputeHook",
                 "hook-name": "Azure Compute",
diff --git 
a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/ai_agents.py
 
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/ai_agents.py
new file mode 100644
index 00000000000..623e629800f
--- /dev/null
+++ 
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/hooks/ai_agents.py
@@ -0,0 +1,454 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, cast
+from urllib.parse import quote
+
+from azure.ai.projects import AIProjectClient
+from azure.ai.projects.aio import AIProjectClient as AsyncAIProjectClient
+from azure.core.exceptions import ResourceNotFoundError
+from azure.core.rest import HttpRequest
+from azure.identity import ClientSecretCredential
+from azure.identity.aio import ClientSecretCredential as 
AsyncClientSecretCredential
+
+from airflow.providers.common.compat.connection import get_async_connection
+from airflow.providers.common.compat.sdk import BaseHook
+from airflow.providers.microsoft.azure._ai_agents import (
+    VERSION_DELETED_STATUS,
+    _get_version_status,
+    _serialize_resource,
+)
+from airflow.providers.microsoft.azure.hooks.base_azure import 
_AZURE_CLOUD_ENVIRONMENTS
+from airflow.providers.microsoft.azure.utils import (
+    add_managed_identity_connection_widgets,
+    get_async_default_azure_credential,
+    get_field,
+    get_sync_default_azure_credential,
+)
+
+if TYPE_CHECKING:
+    from azure.ai.projects.models import (
+        AgentBlueprintReference,
+        AgentDefinition,
+        AgentDetails,
+        AgentVersionDetails,
+        DeleteAgentResponse,
+        DeleteAgentVersionResponse,
+    )
+    from azure.core.credentials import TokenCredential
+    from azure.core.credentials_async import AsyncTokenCredential
+    from pydantic import JsonValue
+
+    from airflow.sdk import Connection
+
+
+DEFAULT_REQUEST_TIMEOUT = 60.0
+
+
+class AzureAIAgentsHook(BaseHook):
+    """
+    Hook for Microsoft Foundry Hosted agents, backed by the 
``azure-ai-projects`` SDK.
+
+    Wraps the Agents operations of :class:`azure.ai.projects.AIProjectClient`:
+    
https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent
+
+    :param azure_ai_agents_conn_id: The Azure AI Agents connection id.
+        Default is ``azure_ai_agents_default``.
+    :param endpoint: Optional Azure AI Foundry project endpoint. If not 
provided, the hook uses the
+        connection host or the ``endpoint`` connection extra. Default is 
``None``.
+    :param api_version: Foundry Agent Service API version. Default is ``v1``.
+    :param timeout: Optional connection/read timeout for service requests, in 
seconds.
+        Default is ``60.0``.
+    """
+
+    conn_name_attr = "azure_ai_agents_conn_id"
+    default_conn_name = "azure_ai_agents_default"
+    conn_type = "azure_ai_agents"
+    hook_name = "Azure AI Foundry Hosted Agents"
+
+    def __init__(
+        self,
+        azure_ai_agents_conn_id: str = default_conn_name,
+        endpoint: str | None = None,
+        api_version: str = "v1",
+        timeout: float | None = DEFAULT_REQUEST_TIMEOUT,
+    ) -> None:
+        super().__init__()
+        self.conn_id = azure_ai_agents_conn_id
+        self.endpoint = endpoint
+        self.api_version = api_version
+        self.timeout = timeout
+        self._sync_client: AIProjectClient | None = None
+
+    @classmethod
+    @add_managed_identity_connection_widgets
+    def get_connection_form_widgets(cls) -> dict[str, Any]:
+        """Return connection widgets to add to connection form."""
+        from flask_appbuilder.fieldwidgets import BS3TextFieldWidget
+        from flask_babel import lazy_gettext
+        from wtforms import StringField
+
+        return {
+            "tenantId": StringField(lazy_gettext("Azure Tenant ID"), 
widget=BS3TextFieldWidget()),
+            "cloud_environment": StringField(
+                lazy_gettext("Azure Cloud Environment"), 
widget=BS3TextFieldWidget()
+            ),
+            "endpoint": StringField(lazy_gettext("Project Endpoint"), 
widget=BS3TextFieldWidget()),
+        }
+
+    @classmethod
+    def get_ui_field_behaviour(cls) -> dict[str, Any]:
+        """Return custom field behaviour."""
+        return {
+            "hidden_fields": ["schema", "port"],
+            "relabeling": {
+                "host": "Project Endpoint",
+                "login": "Azure Client ID",
+                "password": "Azure Secret",
+            },
+            "placeholders": {
+                "host": 
"https://<aiservices-id>.services.ai.azure.com/api/projects/<project-name>",
+                "login": "client_id (token credentials auth)",
+                "password": "secret (token credentials auth)",
+                "tenantId": "tenantId (token credentials auth)",
+                "cloud_environment": "AzurePublicCloud (default) | 
AzureUSGovernment | AzureChinaCloud",
+                "endpoint": "Overrides Project Endpoint from host",
+            },
+        }
+
+    def get_conn(self) -> AIProjectClient:
+        """Return the Azure AI Foundry project client."""
+        if self._sync_client is None:
+            conn = self.get_connection(self.conn_id)
+            # Hosted agents are a Foundry preview feature; allow_preview makes 
the SDK send
+            # the required Foundry-Features request header.
+            self._sync_client = AIProjectClient(
+                endpoint=self._get_endpoint(conn),
+                credential=self._get_credential(conn),
+                api_version=self.api_version,
+                allow_preview=True,
+                connection_timeout=self.timeout,
+                read_timeout=self.timeout,
+            )
+        return self._sync_client
+
+    def _get_endpoint(self, conn: Connection) -> str:
+        endpoint = self.endpoint or conn.host or 
self._get_field(conn.extra_dejson, "endpoint")
+        if not endpoint:
+            raise ValueError(
+                "Azure AI Foundry project endpoint must be provided by the 
hook, connection host, "
+                "or connection extra."
+            )
+        return endpoint.rstrip("/")
+
+    def _get_authority(self, extras: dict[str, Any]) -> str:
+        cloud_env_name = self._get_field(extras, "cloud_environment") or 
"AzurePublicCloud"
+        cloud_env = _AZURE_CLOUD_ENVIRONMENTS.get(
+            cloud_env_name, _AZURE_CLOUD_ENVIRONMENTS["AzurePublicCloud"]
+        )
+        return cloud_env["authority"]
+
+    @staticmethod
+    def _should_use_client_secret_credential(conn: Connection, tenant: str | 
None) -> bool:
+        credential_fields = (conn.login, conn.password, tenant)
+        if any(credential_fields) and not all(credential_fields):
+            raise ValueError(
+                "Azure Client ID, Azure Secret, and Azure Tenant ID must all 
be provided when "
+                "authenticating with a service principal."
+            )
+        return all(credential_fields)
+
+    def _get_credential(self, conn: Connection) -> TokenCredential:
+        extras = conn.extra_dejson
+        tenant = self._get_field(extras, "tenantId")
+
+        if self._should_use_client_secret_credential(conn, tenant):
+            self.log.info("Getting connection using specific credentials.")
+            return ClientSecretCredential(
+                client_id=cast("str", conn.login),
+                client_secret=cast("str", conn.password),
+                tenant_id=cast("str", tenant),
+                authority=self._get_authority(extras),
+            )
+
+        self.log.info("Using DefaultAzureCredential as credential.")
+        return get_sync_default_azure_credential(
+            managed_identity_client_id=self._get_field(extras, 
"managed_identity_client_id"),
+            workload_identity_tenant_id=self._get_field(extras, 
"workload_identity_tenant_id"),
+        )
+
+    def _get_field(self, extras: dict[str, Any], field_name: str) -> str | 
None:
+        return cast(
+            "str | None",
+            get_field(
+                conn_id=self.conn_id,
+                conn_type=self.conn_type,
+                extras=extras,
+                field_name=field_name,
+            ),
+        )
+
+    def create_agent_version(
+        self,
+        agent_name: str,
+        definition: dict[str, Any] | AgentDefinition,
+        *,
+        metadata: dict[str, str] | None = None,
+        description: str | None = None,
+        blueprint_reference: AgentBlueprintReference | None = None,
+    ) -> AgentVersionDetails:
+        """
+        Create a Hosted agent version, creating the Hosted agent itself on 
first use.
+
+        :param agent_name: Hosted agent name.
+        :param definition: Hosted agent container definition.
+        :param metadata: Optional metadata attached to the Hosted agent. 
Default is ``None``.
+        :param description: Optional human-readable Hosted agent description. 
Default is ``None``.
+        :param blueprint_reference: Optional managed identity blueprint 
reference. Default is ``None``.
+        :return: Created Hosted agent version.
+        """
+        return self.get_conn().agents.create_version(
+            agent_name=agent_name,
+            definition=cast("AgentDefinition", definition),
+            metadata=metadata,
+            description=description,
+            blueprint_reference=blueprint_reference,
+        )
+
+    def get_agent_version(self, agent_name: str, agent_version: str) -> 
AgentVersionDetails:
+        """
+        Get a Hosted agent version.
+
+        :param agent_name: Hosted agent name.
+        :param agent_version: Hosted agent version.
+        :return: Hosted agent version details.
+        """
+        return self.get_conn().agents.get_version(agent_name=agent_name, 
agent_version=agent_version)
+
+    def get_agent(self, agent_name: str) -> AgentDetails:
+        """
+        Get a Hosted agent.
+
+        :param agent_name: Hosted agent name.
+        :return: Hosted agent details.
+        """
+        return self.get_conn().agents.get(agent_name=agent_name)
+
+    def delete_agent(self, agent_name: str, *, force: bool = False) -> 
DeleteAgentResponse:
+        """
+        Delete a Hosted agent and all versions.
+
+        :param agent_name: Hosted agent name.
+        :param force: Whether to force deletion when the Hosted agent has 
active sessions.
+            Default is ``False``.
+        :return: Deletion response.
+        """
+        return self.get_conn().agents.delete(agent_name=agent_name, 
force=force)
+
+    def delete_agent_version(
+        self, agent_name: str, agent_version: str, *, force: bool = False
+    ) -> DeleteAgentVersionResponse:
+        """
+        Delete one Hosted agent version.
+
+        :param agent_name: Hosted agent name.
+        :param agent_version: Hosted agent version.
+        :param force: Whether to force deletion when the Hosted agent version 
has active sessions.
+            Default is ``False``.
+        :return: Deletion response.
+        """
+        return self.get_conn().agents.delete_version(
+            agent_name=agent_name, agent_version=agent_version, force=force
+        )
+
+    def is_agent_version_deleted(self, agent_name: str, agent_version: str) -> 
bool:
+        """
+        Return True if the Hosted agent version no longer exists or is deleted.
+
+        :param agent_name: Hosted agent name.
+        :param agent_version: Hosted agent version.
+        :return: ``True`` when the Hosted agent version no longer exists or 
has deleted status.
+        """
+        try:
+            version = self.get_agent_version(agent_name=agent_name, 
agent_version=agent_version)
+        except ResourceNotFoundError:
+            return True
+        return _get_version_status(version) == VERSION_DELETED_STATUS
+
+    def is_agent_deleted(self, agent_name: str) -> bool:
+        """
+        Return True if the Hosted agent no longer exists.
+
+        :param agent_name: Hosted agent name.
+        :return: ``True`` when the Hosted agent no longer exists.
+        """
+        try:
+            self.get_agent(agent_name=agent_name)
+        except ResourceNotFoundError:
+            return True
+        return False
+
+    def invoke_agent_responses(
+        self,
+        agent_name: str,
+        input_data: dict[str, Any],
+        *,
+        user_isolation_key: str | None = None,
+    ) -> dict[str, JsonValue]:
+        """
+        Invoke a Hosted agent through the OpenAI Responses protocol.
+
+        :param agent_name: Hosted agent name.
+        :param input_data: Request payload for the Responses protocol.
+        :param user_isolation_key: Optional user isolation key for endpoint 
resources. Default is ``None``.
+        :return: JSON-compatible Responses protocol response.
+        """
+        request_kwargs: dict[str, Any] = {}
+        if user_isolation_key:
+            request_kwargs["extra_headers"] = {"x-ms-user-isolation-key": 
user_isolation_key}
+        with self.get_conn().get_openai_client(agent_name=agent_name) as 
openai_client:
+            response = openai_client.responses.create(**input_data, 
**request_kwargs)
+        return cast("dict[str, JsonValue]", response.model_dump(mode="json"))
+
+    def invoke_agent_invocations(
+        self,
+        agent_name: str,
+        input_data: dict[str, Any],
+        *,
+        agent_session_id: str | None = None,
+        user_isolation_key: str | None = None,
+    ) -> JsonValue:
+        """
+        Invoke a Hosted agent through the Invocations protocol.
+
+        The SDK does not expose the Invocations protocol yet, so the request 
is sent through
+        the project client pipeline, which handles authentication and preview 
feature headers.
+
+        :param agent_name: Hosted agent name.
+        :param input_data: Request payload for the Invocations protocol.
+        :param agent_session_id: Optional Hosted agent session id. Default is 
``None``.
+        :param user_isolation_key: Optional user isolation key for endpoint 
resources. Default is ``None``.
+        :return: JSON-compatible Invocations protocol payload; its shape is 
defined by the Hosted agent
+            container.
+        """
+        params = {"api-version": self.api_version}
+        if agent_session_id:
+            params["agent_session_id"] = agent_session_id
+        headers = {"x-ms-user-isolation-key": user_isolation_key} if 
user_isolation_key else None
+        request = HttpRequest(
+            "POST",
+            f"/agents/{quote(agent_name, 
safe='')}/endpoint/protocols/invocations",
+            params=params,
+            headers=headers,
+            json=input_data,
+        )
+        response = self.get_conn().send_request(request)
+        response.raise_for_status()
+        if not response.content:
+            return None
+        return _serialize_resource(response.json())
+
+
+class AzureAIAgentsAsyncHook(AzureAIAgentsHook):
+    """
+    Async hook for Microsoft Foundry Hosted agents.
+
+    :param azure_ai_agents_conn_id: The Azure AI Agents connection id.
+        Default is ``azure_ai_agents_default``.
+    :param endpoint: Optional Azure AI Foundry project endpoint override. 
Default is ``None``.
+    :param api_version: Foundry Agent Service API version. Default is ``v1``.
+    :param timeout: Optional connection/read timeout for service requests, in 
seconds.
+        Default is ``60.0``.
+    """
+
+    def __init__(
+        self,
+        azure_ai_agents_conn_id: str = AzureAIAgentsHook.default_conn_name,
+        endpoint: str | None = None,
+        api_version: str = "v1",
+        timeout: float | None = DEFAULT_REQUEST_TIMEOUT,
+    ) -> None:
+        self._async_client: AsyncAIProjectClient | None = None
+        self._async_credential: AsyncTokenCredential | None = None
+        super().__init__(
+            azure_ai_agents_conn_id=azure_ai_agents_conn_id,
+            endpoint=endpoint,
+            api_version=api_version,
+            timeout=timeout,
+        )
+
+    async def close(self) -> None:
+        """Close the async Azure AI Foundry project client and credential."""
+        try:
+            if self._async_client is not None:
+                await self._async_client.close()
+        finally:
+            self._async_client = None
+            if self._async_credential is not None:
+                try:
+                    await self._async_credential.close()
+                finally:
+                    self._async_credential = None
+
+    async def get_async_conn(self) -> AsyncAIProjectClient:
+        """Return the async Azure AI Foundry project client."""
+        if self._async_client is not None:
+            return self._async_client
+
+        conn = await get_async_connection(self.conn_id)
+        self._async_credential = self._get_async_credential(conn)
+        self._async_client = AsyncAIProjectClient(
+            endpoint=self._get_endpoint(conn),
+            credential=self._async_credential,
+            api_version=self.api_version,
+            allow_preview=True,
+            connection_timeout=self.timeout,
+            read_timeout=self.timeout,
+        )
+        return self._async_client
+
+    def _get_async_credential(self, conn: Connection) -> AsyncTokenCredential:
+        extras = conn.extra_dejson
+        tenant = self._get_field(extras, "tenantId")
+
+        if self._should_use_client_secret_credential(conn, tenant):
+            self.log.info("Getting connection using specific credentials.")
+            return AsyncClientSecretCredential(
+                client_id=cast("str", conn.login),
+                client_secret=cast("str", conn.password),
+                tenant_id=cast("str", tenant),
+                authority=self._get_authority(extras),
+            )
+
+        self.log.info("Using DefaultAzureCredential as credential.")
+        return get_async_default_azure_credential(
+            managed_identity_client_id=self._get_field(extras, 
"managed_identity_client_id"),
+            workload_identity_tenant_id=self._get_field(extras, 
"workload_identity_tenant_id"),
+        )
+
+    async def async_get_agent_version(self, agent_name: str, agent_version: 
str) -> AgentVersionDetails:
+        """
+        Get a Hosted agent version asynchronously.
+
+        :param agent_name: Hosted agent name.
+        :param agent_version: Hosted agent version.
+        :return: Hosted agent version details.
+        """
+        client = await self.get_async_conn()
+        return await client.agents.get_version(agent_name=agent_name, 
agent_version=agent_version)
diff --git 
a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/operators/ai_agents.py
 
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/operators/ai_agents.py
new file mode 100644
index 00000000000..69040ee856e
--- /dev/null
+++ 
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/operators/ai_agents.py
@@ -0,0 +1,398 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import time
+from collections.abc import Sequence
+from functools import cached_property
+from typing import TYPE_CHECKING, Any, cast
+
+from airflow.providers.common.compat.sdk import BaseOperator, conf
+from airflow.providers.microsoft.azure._ai_agents import (
+    VERSION_FAILURE_STATUSES,
+    VERSION_INTERMEDIATE_STATUSES,
+    VERSION_SUCCESS_STATUSES,
+    _get_agent_version,
+    _get_resource_attr,
+    _get_version_status,
+    _serialize_resource,
+)
+from airflow.providers.microsoft.azure.hooks.ai_agents import AzureAIAgentsHook
+from airflow.providers.microsoft.azure.triggers.ai_agents import 
AzureAIAgentVersionTrigger
+
+if TYPE_CHECKING:
+    from azure.ai.projects.models import AgentBlueprintReference
+    from pydantic import JsonValue
+
+    from airflow.sdk import Context
+
+
+class CreateAzureAIAgentOperator(BaseOperator):
+    """
+    Create an Azure AI Hosted agent version from a container image definition.
+
+    Creating a version also creates the Hosted agent itself on first use.
+
+    :param agent_name: Hosted agent name.
+    :param definition: Hosted agent definition. Include 
``container_configuration.image`` with
+        the Azure Container Registry image URI.
+    :param metadata: Optional metadata attached to the Hosted agent. Default 
is ``None``.
+    :param description: Optional human-readable Hosted agent description. 
Default is ``None``.
+    :param blueprint_reference: Optional managed identity blueprint reference. 
Default is ``None``.
+    :param wait_for_completion: Whether to wait until the created version 
reaches ``active``.
+        Default is ``True``.
+    :param poll_interval: Time in seconds between status checks. Default is 
``30.0``.
+    :param timeout: Time in seconds to wait for the version to become active. 
Default is ``3600``.
+    :param deferrable: Run in deferrable mode when ``wait_for_completion`` is 
``True``.
+        Default is configured by ``operators.default_deferrable``.
+    :param azure_ai_agents_conn_id: Azure AI Agents connection id.
+        Default is ``azure_ai_agents_default``.
+    :param endpoint: Optional Azure AI Foundry project endpoint override. 
Default is ``None``.
+    :param api_version: Foundry Agent Service API version. Default is ``v1``.
+    """
+
+    template_fields: Sequence[str] = (
+        "agent_name",
+        "definition",
+        "metadata",
+        "description",
+        "blueprint_reference",
+        "azure_ai_agents_conn_id",
+        "endpoint",
+        "api_version",
+    )
+    template_fields_renderers = {
+        "definition": "json",
+        "metadata": "json",
+        "blueprint_reference": "json",
+    }
+    ui_color = "#0678d4"
+
+    def __init__(
+        self,
+        *,
+        agent_name: str,
+        definition: dict[str, Any],
+        metadata: dict[str, str] | None = None,
+        description: str | None = None,
+        blueprint_reference: AgentBlueprintReference | None = None,
+        wait_for_completion: bool = True,
+        poll_interval: float = 30.0,
+        timeout: float = 60 * 60,
+        deferrable: bool = conf.getboolean("operators", "default_deferrable", 
fallback=False),
+        azure_ai_agents_conn_id: str = AzureAIAgentsHook.default_conn_name,
+        endpoint: str | None = None,
+        api_version: str = "v1",
+        **kwargs,
+    ) -> None:
+        super().__init__(**kwargs)
+        self.agent_name = agent_name
+        self.definition = definition
+        self.metadata = metadata
+        self.description = description
+        self.blueprint_reference = blueprint_reference
+        self.wait_for_completion = wait_for_completion
+        self.poll_interval = poll_interval
+        self.timeout = timeout
+        self.deferrable = deferrable
+        self.azure_ai_agents_conn_id = azure_ai_agents_conn_id
+        self.endpoint = endpoint
+        self.api_version = api_version
+
+    @cached_property
+    def hook(self) -> AzureAIAgentsHook:
+        """Create and return an AzureAIAgentsHook."""
+        return AzureAIAgentsHook(
+            azure_ai_agents_conn_id=self.azure_ai_agents_conn_id,
+            endpoint=self.endpoint,
+            api_version=self.api_version,
+        )
+
+    def execute(self, context: Context) -> dict[str, JsonValue]:
+        """Create an Azure AI Hosted agent version and optionally wait for it 
to become active."""
+        self.log.info("Creating Azure AI Hosted agent %s version.", 
self.agent_name)
+        version = self.hook.create_agent_version(
+            agent_name=self.agent_name,
+            definition=self.definition,
+            metadata=self.metadata,
+            description=self.description,
+            blueprint_reference=self.blueprint_reference,
+        )
+        if not self.wait_for_completion:
+            return cast("dict[str, JsonValue]", _serialize_resource(version))
+        agent_version = _get_agent_version(version)
+        if self.deferrable:
+            self.defer(
+                timeout=self.execution_timeout,
+                trigger=AzureAIAgentVersionTrigger(
+                    azure_ai_agents_conn_id=self.azure_ai_agents_conn_id,
+                    endpoint=self.endpoint,
+                    api_version=self.api_version,
+                    agent_name=self.agent_name,
+                    agent_version=agent_version,
+                    timeout=self.timeout,
+                    poll_interval=self.poll_interval,
+                ),
+                method_name="execute_complete",
+            )
+        return self._wait_for_version(agent_version=agent_version)
+
+    def _wait_for_version(self, *, agent_version: str) -> dict[str, JsonValue]:
+        end_time = time.monotonic() + self.timeout
+        while True:
+            version = self.hook.get_agent_version(agent_name=self.agent_name, 
agent_version=agent_version)
+            status = _get_version_status(version)
+            if status in VERSION_SUCCESS_STATUSES:
+                return cast("dict[str, JsonValue]", 
_serialize_resource(version))
+            if status in VERSION_FAILURE_STATUSES:
+                error = _get_resource_attr(version, "error") or "No error 
details were returned"
+                raise RuntimeError(
+                    f"Azure AI Hosted agent {self.agent_name} version 
{agent_version} failed: {error}."
+                )
+            if status not in VERSION_INTERMEDIATE_STATUSES:
+                raise RuntimeError(
+                    f"Azure AI Hosted agent {self.agent_name} version 
{agent_version} "
+                    f"reached unknown status {status}."
+                )
+            if time.monotonic() >= end_time:
+                raise TimeoutError(
+                    f"Timeout waiting for Azure AI Hosted agent 
{self.agent_name} version {agent_version}."
+                )
+
+            self.log.info(
+                "Azure AI Hosted agent %s version %s is in status %s. Sleeping 
for %s seconds.",
+                self.agent_name,
+                agent_version,
+                status,
+                self.poll_interval,
+            )
+            time.sleep(self.poll_interval)
+
+    def execute_complete(self, context: Context, event: dict[str, Any] | None) 
-> dict[str, JsonValue]:
+        """Resume after the version trigger completes."""
+        if event is None:
+            raise RuntimeError("Trigger returned no event.")
+
+        status = event.get("status")
+        message = event.get("message", "No message returned from trigger.")
+        if status == "success":
+            if "version" not in event:
+                raise RuntimeError("Trigger success event did not include 
version payload.")
+            self.log.info(message)
+            return event["version"]
+        if status == "timeout":
+            raise TimeoutError(message)
+        if status == "error":
+            raise RuntimeError(message)
+        raise ValueError(f"Unexpected trigger event status: {status!r}")
+
+
+class UpdateAzureAIAgentOperator(CreateAzureAIAgentOperator):
+    """
+    Create a new version of an existing Azure AI Hosted agent.
+
+    The Hosted agent API publishes updates as new immutable versions, so this 
operator accepts
+    the same parameters as :class:`CreateAzureAIAgentOperator`.
+    """
+
+
+class RunAzureAIAgentOperator(BaseOperator):
+    """
+    Invoke an Azure AI Hosted agent.
+
+    :param agent_name: Hosted agent name.
+    :param input_data: Request payload for the selected protocol.
+    :param protocol: Hosted agent protocol to use. Supported values are 
``responses`` and
+        ``invocations``. Default is ``responses``.
+    :param agent_session_id: Optional session id used by the ``invocations`` 
protocol.
+        Default is ``None``.
+    :param user_isolation_key: Optional per-user isolation key for 
endpoint-scoped resources.
+        Default is ``None``.
+    :param azure_ai_agents_conn_id: Azure AI Agents connection id.
+        Default is ``azure_ai_agents_default``.
+    :param endpoint: Optional Azure AI Foundry project endpoint override. 
Default is ``None``.
+    :param api_version: Foundry Agent Service API version. Default is ``v1``.
+    """
+
+    template_fields: Sequence[str] = (
+        "agent_name",
+        "input_data",
+        "protocol",
+        "agent_session_id",
+        "user_isolation_key",
+        "azure_ai_agents_conn_id",
+        "endpoint",
+        "api_version",
+    )
+    template_fields_renderers = {"input_data": "json"}
+    ui_color = "#0678d4"
+
+    def __init__(
+        self,
+        *,
+        agent_name: str,
+        input_data: dict[str, Any],
+        protocol: str = "responses",
+        agent_session_id: str | None = None,
+        user_isolation_key: str | None = None,
+        azure_ai_agents_conn_id: str = AzureAIAgentsHook.default_conn_name,
+        endpoint: str | None = None,
+        api_version: str = "v1",
+        **kwargs,
+    ) -> None:
+        super().__init__(**kwargs)
+        self.agent_name = agent_name
+        self.input_data = input_data
+        self.protocol = protocol
+        self.agent_session_id = agent_session_id
+        self.user_isolation_key = user_isolation_key
+        self.azure_ai_agents_conn_id = azure_ai_agents_conn_id
+        self.endpoint = endpoint
+        self.api_version = api_version
+
+    @cached_property
+    def hook(self) -> AzureAIAgentsHook:
+        """Create and return an AzureAIAgentsHook."""
+        return AzureAIAgentsHook(
+            azure_ai_agents_conn_id=self.azure_ai_agents_conn_id,
+            endpoint=self.endpoint,
+            api_version=self.api_version,
+        )
+
+    def execute(self, context: Context) -> JsonValue:
+        """Invoke an Azure AI Hosted agent and return the response payload."""
+        self.log.info("Invoking Azure AI Hosted agent %s with %s protocol.", 
self.agent_name, self.protocol)
+        if self.protocol == "responses":
+            return self.hook.invoke_agent_responses(
+                agent_name=self.agent_name,
+                input_data=self.input_data,
+                user_isolation_key=self.user_isolation_key,
+            )
+        if self.protocol == "invocations":
+            return self.hook.invoke_agent_invocations(
+                agent_name=self.agent_name,
+                input_data=self.input_data,
+                agent_session_id=self.agent_session_id,
+                user_isolation_key=self.user_isolation_key,
+            )
+        raise ValueError("protocol must be either 'responses' or 
'invocations'.")
+
+
+class DeleteAzureAIAgentOperator(BaseOperator):
+    """
+    Delete an Azure AI Hosted agent or one Hosted agent version.
+
+    :param agent_name: Hosted agent name.
+    :param agent_version: Optional Hosted agent version. When omitted, the 
whole agent is deleted.
+        Default is ``None``.
+    :param force: Whether to cascade-delete active sessions when deleting the 
agent or version.
+        Default is ``False``.
+    :param wait_for_completion: Whether to wait until the resource is deleted. 
Default is ``True``.
+    :param poll_interval: Time in seconds between status checks. Default is 
``30.0``.
+    :param timeout: Time in seconds to wait for deletion to complete. Default 
is ``3600``.
+    :param azure_ai_agents_conn_id: Azure AI Agents connection id.
+        Default is ``azure_ai_agents_default``.
+    :param endpoint: Optional Azure AI Foundry project endpoint override. 
Default is ``None``.
+    :param api_version: Foundry Agent Service API version. Default is ``v1``.
+    """
+
+    template_fields: Sequence[str] = (
+        "agent_name",
+        "agent_version",
+        "azure_ai_agents_conn_id",
+        "endpoint",
+        "api_version",
+    )
+    ui_color = "#0678d4"
+
+    def __init__(
+        self,
+        *,
+        agent_name: str,
+        agent_version: str | None = None,
+        force: bool = False,
+        wait_for_completion: bool = True,
+        poll_interval: float = 30.0,
+        timeout: float = 60 * 60,
+        azure_ai_agents_conn_id: str = AzureAIAgentsHook.default_conn_name,
+        endpoint: str | None = None,
+        api_version: str = "v1",
+        **kwargs,
+    ) -> None:
+        super().__init__(**kwargs)
+        self.agent_name = agent_name
+        self.agent_version = agent_version
+        self.force = force
+        self.wait_for_completion = wait_for_completion
+        self.poll_interval = poll_interval
+        self.timeout = timeout
+        self.azure_ai_agents_conn_id = azure_ai_agents_conn_id
+        self.endpoint = endpoint
+        self.api_version = api_version
+
+    @cached_property
+    def hook(self) -> AzureAIAgentsHook:
+        """Create and return an AzureAIAgentsHook."""
+        return AzureAIAgentsHook(
+            azure_ai_agents_conn_id=self.azure_ai_agents_conn_id,
+            endpoint=self.endpoint,
+            api_version=self.api_version,
+        )
+
+    def execute(self, context: Context) -> dict[str, JsonValue]:
+        """Delete an Azure AI Hosted agent or version and optionally wait for 
deletion."""
+        delete_response: Any
+        if self.agent_version is None:
+            self.log.info("Deleting Azure AI Hosted agent %s.", 
self.agent_name)
+            delete_response = 
self.hook.delete_agent(agent_name=self.agent_name, force=self.force)
+        else:
+            self.log.info(
+                "Deleting Azure AI Hosted agent %s version %s.", 
self.agent_name, self.agent_version
+            )
+            delete_response = self.hook.delete_agent_version(
+                agent_name=self.agent_name, agent_version=self.agent_version, 
force=self.force
+            )
+
+        if self.wait_for_completion:
+            self._wait_for_deletion()
+        return cast("dict[str, JsonValue]", 
_serialize_resource(delete_response))
+
+    def _wait_for_deletion(self) -> None:
+        end_time = time.monotonic() + self.timeout
+        resource_description = f"agent {self.agent_name}"
+        if self.agent_version is not None:
+            resource_description += f" version {self.agent_version}"
+        while True:
+            if self.agent_version is None:
+                is_deleted = 
self.hook.is_agent_deleted(agent_name=self.agent_name)
+            else:
+                is_deleted = self.hook.is_agent_version_deleted(
+                    agent_name=self.agent_name, 
agent_version=self.agent_version
+                )
+            if is_deleted:
+                self.log.info("Azure AI Hosted %s was deleted.", 
resource_description)
+                return
+            if time.monotonic() >= end_time:
+                raise TimeoutError(f"Timeout waiting for Azure AI Hosted 
{resource_description} deletion.")
+
+            self.log.info(
+                "Azure AI Hosted %s is still retrievable. Sleeping for %s 
seconds.",
+                resource_description,
+                self.poll_interval,
+            )
+            time.sleep(self.poll_interval)
diff --git 
a/providers/microsoft/azure/src/airflow/providers/microsoft/azure/triggers/ai_agents.py
 
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/triggers/ai_agents.py
new file mode 100644
index 00000000000..704c4af7da4
--- /dev/null
+++ 
b/providers/microsoft/azure/src/airflow/providers/microsoft/azure/triggers/ai_agents.py
@@ -0,0 +1,175 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import asyncio
+import time
+from collections.abc import AsyncIterator
+from typing import Any
+
+from airflow.providers.microsoft.azure._ai_agents import (
+    VERSION_FAILURE_STATUSES,
+    VERSION_INTERMEDIATE_STATUSES,
+    VERSION_SUCCESS_STATUSES,
+    _get_resource_attr,
+    _get_version_status,
+    _serialize_resource,
+)
+from airflow.providers.microsoft.azure.hooks.ai_agents import 
AzureAIAgentsAsyncHook
+from airflow.triggers.base import BaseTrigger, TriggerEvent
+
+
+class AzureAIAgentVersionTrigger(BaseTrigger):
+    """
+    Trigger that polls an Azure AI Hosted agent version until it becomes 
active.
+
+    :param azure_ai_agents_conn_id: Azure AI Agents connection id.
+    :param endpoint: Optional Azure AI Foundry project endpoint override. 
Default is ``None``.
+    :param api_version: Foundry Agent Service API version.
+    :param agent_name: Hosted agent name.
+    :param agent_version: Hosted agent version to poll.
+    :param timeout: Time in seconds to wait for the version to become active.
+    :param poll_interval: Poll interval in seconds.
+    """
+
+    def __init__(
+        self,
+        *,
+        azure_ai_agents_conn_id: str,
+        endpoint: str | None = None,
+        api_version: str,
+        agent_name: str,
+        agent_version: str,
+        timeout: float,
+        poll_interval: float,
+    ) -> None:
+        super().__init__()
+        self.azure_ai_agents_conn_id = azure_ai_agents_conn_id
+        self.endpoint = endpoint
+        self.api_version = api_version
+        self.agent_name = agent_name
+        self.agent_version = agent_version
+        self.timeout = timeout
+        self.poll_interval = poll_interval
+
+    def serialize(self) -> tuple[str, dict[str, Any]]:
+        """Serialize trigger arguments and classpath."""
+        return (
+            f"{self.__class__.__module__}.{self.__class__.__name__}",
+            {
+                "azure_ai_agents_conn_id": self.azure_ai_agents_conn_id,
+                "endpoint": self.endpoint,
+                "api_version": self.api_version,
+                "agent_name": self.agent_name,
+                "agent_version": self.agent_version,
+                "timeout": self.timeout,
+                "poll_interval": self.poll_interval,
+            },
+        )
+
+    def _build_trigger_event(self, version: Any) -> TriggerEvent | None:
+        """Build a terminal TriggerEvent for a Hosted agent version."""
+        status = _get_version_status(version)
+        if status in VERSION_INTERMEDIATE_STATUSES:
+            return None
+
+        serialized_version = _serialize_resource(version)
+        if status in VERSION_SUCCESS_STATUSES:
+            return TriggerEvent(
+                {
+                    "status": "success",
+                    "message": (
+                        f"Azure AI Hosted agent {self.agent_name} version 
{self.agent_version} is active."
+                    ),
+                    "version": serialized_version,
+                }
+            )
+        if status in VERSION_FAILURE_STATUSES:
+            error = _get_resource_attr(version, "error") or "No error details 
were returned"
+            return TriggerEvent(
+                {
+                    "status": "error",
+                    "message": (
+                        f"Azure AI Hosted agent {self.agent_name} version 
{self.agent_version} failed: "
+                        f"{error}."
+                    ),
+                    "version": serialized_version,
+                }
+            )
+        return TriggerEvent(
+            {
+                "status": "error",
+                "message": (
+                    f"Azure AI Hosted agent {self.agent_name} version 
{self.agent_version} "
+                    f"reached unknown status {status}."
+                ),
+                "version": serialized_version,
+            }
+        )
+
+    async def run(self) -> AsyncIterator[TriggerEvent]:
+        """Poll the Hosted agent version status until terminal state or 
timeout."""
+        hook = AzureAIAgentsAsyncHook(
+            azure_ai_agents_conn_id=self.azure_ai_agents_conn_id,
+            endpoint=self.endpoint,
+            api_version=self.api_version,
+        )
+
+        try:
+            end_time = time.monotonic() + self.timeout
+            while True:
+                version = await hook.async_get_agent_version(
+                    agent_name=self.agent_name,
+                    agent_version=self.agent_version,
+                )
+                event = self._build_trigger_event(version)
+                if event:
+                    yield event
+                    return
+                if time.monotonic() >= end_time:
+                    yield TriggerEvent(
+                        {
+                            "status": "timeout",
+                            "message": (
+                                f"Timeout waiting for Azure AI Hosted agent 
{self.agent_name} "
+                                f"version {self.agent_version}."
+                            ),
+                            "version": {"name": self.agent_name, "version": 
self.agent_version},
+                        }
+                    )
+                    return
+
+                await asyncio.sleep(self.poll_interval)
+        except Exception as e:
+            self.log.exception(
+                "Exception while polling Azure AI Hosted agent %s version %s.",
+                self.agent_name,
+                self.agent_version,
+            )
+            yield TriggerEvent(
+                {
+                    "status": "error",
+                    "message": (
+                        f"Failed while polling Azure AI Hosted agent 
{self.agent_name} "
+                        f"version {self.agent_version}: {e}"
+                    ),
+                    "version": {"name": self.agent_name, "version": 
self.agent_version},
+                }
+            )
+        finally:
+            await hook.close()
diff --git 
a/providers/microsoft/azure/tests/system/microsoft/azure/example_azure_ai_agents.py
 
b/providers/microsoft/azure/tests/system/microsoft/azure/example_azure_ai_agents.py
new file mode 100644
index 00000000000..aa9e888e423
--- /dev/null
+++ 
b/providers/microsoft/azure/tests/system/microsoft/azure/example_azure_ai_agents.py
@@ -0,0 +1,230 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""
+System test for Azure AI Foundry Hosted agent operators.
+
+Requires a real Azure AI Foundry project and a Hosted agent container image 
pushed to
+Azure Container Registry:
+
+* ``AIRFLOW_CONN_AZURE_AI_AGENTS_DEFAULT``: Azure AI Agents connection URI.
+* ``AZURE_AI_AGENTS_ENDPOINT``: Azure AI Foundry project endpoint.
+* ``AZURE_AI_AGENTS_CONTAINER_IMAGE``: Hosted agent container image URI.
+* ``AZURE_AI_AGENTS_MODEL_DEPLOYMENT_NAME``: Model deployment available in the 
project.
+* ``AZURE_AI_AGENTS_RUN_PROTOCOL``: Optional runtime protocol to invoke 
(``responses`` or ``invocations``).
+* ``AZURE_AI_AGENTS_USE_MODEL``: Optional, defaults to ``false`` for a 
deterministic smoke test.
+* ``AZURE_AI_AGENTS_USE_MOCKS``: Optional, defaults to ``true`` so the sample 
agent does not need
+  external service credentials.
+"""
+
+from __future__ import annotations
+
+import atexit
+import json
+import os
+import tempfile
+from datetime import datetime
+from typing import Any
+
+from airflow.providers.microsoft.azure.operators.ai_agents import (
+    CreateAzureAIAgentOperator,
+    DeleteAzureAIAgentOperator,
+    RunAzureAIAgentOperator,
+    UpdateAzureAIAgentOperator,
+)
+
+from tests_common.test_utils.version_compat import AIRFLOW_V_3_0_PLUS
+
+if AIRFLOW_V_3_0_PLUS:
+    from airflow.sdk import DAG, chain
+else:
+    from airflow.models.baseoperator import chain  # type: 
ignore[attr-defined,no-redef]
+    from airflow.models.dag import DAG  # type: 
ignore[attr-defined,no-redef,assignment]
+
+try:
+    from airflow.sdk import TriggerRule
+except ImportError:
+    from airflow.utils.trigger_rule import TriggerRule  # type: 
ignore[no-redef,attr-defined]
+
+DAG_ID = "example_azure_ai_agents"
+ENV_ID = os.environ.get("SYSTEM_TESTS_ENV_ID") or "default"
+
+
+def _get_env(name: str, default: str = "") -> str:
+    return os.environ.get(name) or os.environ.get(f"AIRFLOW_VAR_{name}", 
default)
+
+
+AZURE_AI_AGENTS_CONN_ID = _get_env("AZURE_AI_AGENTS_CONN_ID", 
"azure_ai_agents_default")
+AZURE_AI_AGENTS_CONN_URI = _get_env("AIRFLOW_CONN_AZURE_AI_AGENTS_DEFAULT")
+ENDPOINT = _get_env("AZURE_AI_AGENTS_ENDPOINT")
+MODEL_DEPLOYMENT_NAME = _get_env("AZURE_AI_AGENTS_MODEL_DEPLOYMENT_NAME", 
"gpt-4o")
+CONTAINER_IMAGE = _get_env(
+    "AZURE_AI_AGENTS_CONTAINER_IMAGE", 
"myregistry.azurecr.io/airflow-hosted-agent:latest"
+)
+AGENT_NAME = _get_env("AZURE_AI_AGENT_NAME", f"airflow-ai-agent-{ENV_ID}")
+RUN_AGENT_PROTOCOL = _get_env("AZURE_AI_AGENTS_RUN_PROTOCOL").lower()
+if RUN_AGENT_PROTOCOL not in {"", "responses", "invocations"}:
+    raise RuntimeError("AZURE_AI_AGENTS_RUN_PROTOCOL must be either 
'responses' or 'invocations'.")
+HOSTED_AGENT_PROTOCOL = RUN_AGENT_PROTOCOL or "responses"
+run_agent: RunAzureAIAgentOperator | None
+
+HOSTED_AGENT_DEFINITION: dict[str, Any] = {
+    "kind": "hosted",
+    "container_configuration": {
+        "image": CONTAINER_IMAGE,
+    },
+    "cpu": "1",
+    "memory": "2Gi",
+    "protocol_versions": [
+        {"protocol": HOSTED_AGENT_PROTOCOL, "version": "1.0.0"},
+    ],
+    "environment_variables": {
+        "AZURE_AI_MODEL_DEPLOYMENT_NAME": MODEL_DEPLOYMENT_NAME,
+        "AIRFLOW_AGENT_FOUNDRY_PROJECT_ENDPOINT": ENDPOINT,
+        "AIRFLOW_AGENT_USE_MODEL": _get_env("AZURE_AI_AGENTS_USE_MODEL", 
"false"),
+        "AIRFLOW_AGENT_USE_MOCKS": _get_env("AZURE_AI_AGENTS_USE_MOCKS", 
"true"),
+    },
+}
+
+UPDATED_AGENT_DEFINITION: dict[str, Any] = {
+    **HOSTED_AGENT_DEFINITION,
+    "environment_variables": {
+        **HOSTED_AGENT_DEFINITION["environment_variables"],
+        "AIRFLOW_AGENT_BEHAVIOR": "troubleshoot-airflow-task-failures",
+    },
+}
+
+SMOKE_FAILURE_CONTEXT = json.dumps(
+    {
+        "dag_id": "azure_ai_agents_demo_failing_etl",
+        "run_id": "manual__azure_ai_agents_smoke",
+        "dag_file": "azure_ai_agents/demo_failing_etl.py",
+        "failed_task": {
+            "task_id": "transform",
+            "state": "failed",
+            "try_number": 1,
+        },
+        "log_excerpt": "KeyError: 'rowz'",
+    }
+)
+
+RUN_AGENT_PROMPT = (
+    "Analyze this Airflow failure context and return JSON with summary, "
+    "root_cause, and suggested_fix. Do not claim PR or Slack actions unless "
+    "the hosted agent tools actually performed them.\n\n"
+    f"{SMOKE_FAILURE_CONTEXT}"
+)
+
+RUN_AGENT_INPUT = {"input" if RUN_AGENT_PROTOCOL == "responses" else 
"message": RUN_AGENT_PROMPT}
+
+
+def create_connection_file(conn_id_name: str, conn_uri: str) -> str | None:
+    if not conn_uri:
+        return None
+    with tempfile.NamedTemporaryFile("w", suffix=".env", delete=False) as 
conn_file:
+        conn_file.write(f"{conn_id_name}={conn_uri}\n")
+        atexit.register(os.unlink, conn_file.name)
+        return conn_file.name
+
+
+CONN_FILE_PATH = create_connection_file(AZURE_AI_AGENTS_CONN_ID, 
AZURE_AI_AGENTS_CONN_URI)
+
+
+with DAG(
+    dag_id=DAG_ID,
+    schedule="@once",
+    start_date=datetime(2021, 1, 1),
+    catchup=False,
+) as dag:
+    # [START howto_operator_azure_ai_agent_create]
+    create_agent = CreateAzureAIAgentOperator(
+        task_id="create_agent",
+        agent_name=AGENT_NAME,
+        definition=HOSTED_AGENT_DEFINITION,
+        poll_interval=10,
+        timeout=900,
+        deferrable=False,
+        azure_ai_agents_conn_id=AZURE_AI_AGENTS_CONN_ID,
+        endpoint=ENDPOINT,
+    )
+    # [END howto_operator_azure_ai_agent_create]
+
+    # [START howto_operator_azure_ai_agent_update]
+    update_agent = UpdateAzureAIAgentOperator(
+        task_id="update_agent",
+        agent_name=AGENT_NAME,
+        definition=UPDATED_AGENT_DEFINITION,
+        poll_interval=10,
+        timeout=900,
+        deferrable=False,
+        azure_ai_agents_conn_id=AZURE_AI_AGENTS_CONN_ID,
+        endpoint=ENDPOINT,
+    )
+    # [END howto_operator_azure_ai_agent_update]
+
+    # [START howto_operator_azure_ai_agent_update_deferrable]
+    update_agent_deferrable = UpdateAzureAIAgentOperator(
+        task_id="update_agent_deferrable",
+        agent_name=AGENT_NAME,
+        definition=UPDATED_AGENT_DEFINITION,
+        poll_interval=10,
+        timeout=900,
+        deferrable=True,
+        azure_ai_agents_conn_id=AZURE_AI_AGENTS_CONN_ID,
+        endpoint=ENDPOINT,
+    )
+    # [END howto_operator_azure_ai_agent_update_deferrable]
+
+    if RUN_AGENT_PROTOCOL:
+        # [START howto_operator_azure_ai_agent_run]
+        run_agent = RunAzureAIAgentOperator(
+            task_id="run_agent",
+            agent_name=AGENT_NAME,
+            protocol=RUN_AGENT_PROTOCOL,
+            input_data=RUN_AGENT_INPUT,
+            azure_ai_agents_conn_id=AZURE_AI_AGENTS_CONN_ID,
+            endpoint=ENDPOINT,
+        )
+        # [END howto_operator_azure_ai_agent_run]
+    else:
+        run_agent = None
+
+    # [START howto_operator_azure_ai_agent_delete]
+    delete_agent = DeleteAzureAIAgentOperator(
+        task_id="delete_agent",
+        agent_name=AGENT_NAME,
+        force=True,
+        poll_interval=10,
+        timeout=900,
+        trigger_rule=TriggerRule.ALL_DONE,
+        azure_ai_agents_conn_id=AZURE_AI_AGENTS_CONN_ID,
+        endpoint=ENDPOINT,
+    )
+    # [END howto_operator_azure_ai_agent_delete]
+
+    if run_agent is None:
+        chain(create_agent, update_agent, update_agent_deferrable, 
delete_agent)
+    else:
+        chain(create_agent, update_agent, update_agent_deferrable, run_agent, 
delete_agent)
+
+    from tests_common.test_utils.watcher import watcher
+
+    list(dag.tasks) >> watcher()
+
+from tests_common.test_utils.system_tests import get_test_run  # noqa: E402
+
+test_run = get_test_run(dag, conn_file_path=CONN_FILE_PATH)
diff --git 
a/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_ai_agents.py 
b/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_ai_agents.py
new file mode 100644
index 00000000000..95d74dacae2
--- /dev/null
+++ 
b/providers/microsoft/azure/tests/unit/microsoft/azure/hooks/test_ai_agents.py
@@ -0,0 +1,475 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from unittest import mock
+
+import pytest
+from azure.core.exceptions import ResourceNotFoundError
+
+from airflow.models import Connection
+from airflow.providers.microsoft.azure.hooks.ai_agents import (
+    DEFAULT_REQUEST_TIMEOUT,
+    AzureAIAgentsAsyncHook,
+    AzureAIAgentsHook,
+)
+
+MODULE = "airflow.providers.microsoft.azure.hooks.ai_agents"
+CONN_ID = "azure_ai_agents_test"
+ENDPOINT = "https://test.services.ai.azure.com/api/projects/test-project";
+AGENT_NAME = "agent-123"
+DEFINITION = {
+    "kind": "hosted",
+    "container_configuration": {"image": "registry.azurecr.io/agent:v1"},
+    "cpu": "1",
+    "memory": "2Gi",
+    "protocol_versions": [{"protocol": "responses", "version": "1.0.0"}],
+}
+METADATA = {"team": "airflow"}
+DESCRIPTION = "Airflow hosted agent"
+BLUEPRINT_REFERENCE = {"type": "ManagedAgentIdentityBlueprint", 
"blueprint_id": "blueprint-1"}
+
+
[email protected]
+def hook_with_mocked_client():
+    hook = AzureAIAgentsHook(azure_ai_agents_conn_id=CONN_ID)
+    hook._sync_client = mock.MagicMock()
+    return hook
+
+
+class TestAzureAIAgentsHook:
+    def test_connection_form_widgets(self):
+        pytest.importorskip("flask_appbuilder")
+        widgets = AzureAIAgentsHook.get_connection_form_widgets()
+
+        assert "tenantId" in widgets
+        assert "cloud_environment" in widgets
+        assert "endpoint" in widgets
+        assert "managed_identity_client_id" in widgets
+        assert "workload_identity_tenant_id" in widgets
+
+    def test_ui_field_behaviour(self):
+        behaviour = AzureAIAgentsHook.get_ui_field_behaviour()
+
+        assert behaviour["hidden_fields"] == ["schema", "port"]
+        assert behaviour["relabeling"]["host"] == "Project Endpoint"
+        assert behaviour["relabeling"]["login"] == "Azure Client ID"
+        assert behaviour["relabeling"]["password"] == "Azure Secret"
+
+    @mock.patch(f"{MODULE}.ClientSecretCredential", autospec=True)
+    def test_get_credential_uses_client_secret_credential(self, 
mock_credential_cls, create_mock_connection):
+        create_mock_connection(
+            Connection(
+                conn_id=CONN_ID,
+                conn_type="azure_ai_agents",
+                host=ENDPOINT,
+                login="client-id",
+                password="client-secret",
+                extra={"tenantId": "tenant-id", "cloud_environment": 
"AzureUSGovernment"},
+            )
+        )
+        hook = AzureAIAgentsHook(azure_ai_agents_conn_id=CONN_ID)
+
+        result = hook._get_credential(hook.get_connection(CONN_ID))
+
+        assert result == mock_credential_cls.return_value
+        mock_credential_cls.assert_called_once_with(
+            client_id="client-id",
+            client_secret="client-secret",
+            tenant_id="tenant-id",
+            authority="login.microsoftonline.us",
+        )
+
+    @pytest.mark.parametrize(
+        ("login", "password", "tenant"),
+        [
+            ("client-id", None, None),
+            (None, "client-secret", None),
+            (None, None, "tenant-id"),
+            ("client-id", "client-secret", None),
+            ("client-id", None, "tenant-id"),
+            (None, "client-secret", "tenant-id"),
+        ],
+    )
+    def test_get_credential_rejects_partial_service_principal(self, login, 
password, tenant):
+        conn = Connection(
+            conn_id=CONN_ID,
+            conn_type="azure_ai_agents",
+            login=login,
+            password=password,
+            extra={"tenantId": tenant} if tenant else None,
+        )
+        hook = AzureAIAgentsHook(azure_ai_agents_conn_id=CONN_ID)
+
+        with pytest.raises(ValueError, match="must all be provided"):
+            hook._get_credential(conn)
+
+    @mock.patch(f"{MODULE}.get_sync_default_azure_credential", autospec=True)
+    @mock.patch(f"{MODULE}.AIProjectClient", autospec=True)
+    def test_client_uses_default_credential_and_endpoint_extra(
+        self, mock_client_cls, mock_default_credential, create_mock_connection
+    ):
+        create_mock_connection(
+            Connection(
+                conn_id=CONN_ID,
+                conn_type="azure_ai_agents",
+                extra={
+                    "endpoint": ENDPOINT,
+                    "managed_identity_client_id": "managed-identity-client-id",
+                    "workload_identity_tenant_id": 
"workload-identity-tenant-id",
+                },
+            )
+        )
+        hook = AzureAIAgentsHook(azure_ai_agents_conn_id=CONN_ID)
+
+        result = hook.get_conn()
+
+        assert result == mock_client_cls.return_value
+        mock_default_credential.assert_called_once_with(
+            managed_identity_client_id="managed-identity-client-id",
+            workload_identity_tenant_id="workload-identity-tenant-id",
+        )
+        mock_client_cls.assert_called_once_with(
+            endpoint=ENDPOINT,
+            credential=mock_default_credential.return_value,
+            api_version="v1",
+            allow_preview=True,
+            connection_timeout=DEFAULT_REQUEST_TIMEOUT,
+            read_timeout=DEFAULT_REQUEST_TIMEOUT,
+        )
+
+    @mock.patch(f"{MODULE}.get_sync_default_azure_credential", autospec=True)
+    @mock.patch(f"{MODULE}.AIProjectClient", autospec=True)
+    def test_client_uses_custom_api_version_and_timeout(
+        self, mock_client_cls, mock_default_credential, create_mock_connection
+    ):
+        create_mock_connection(Connection(conn_id=CONN_ID, 
conn_type="azure_ai_agents", host=ENDPOINT))
+        hook = AzureAIAgentsHook(azure_ai_agents_conn_id=CONN_ID, 
api_version="v2", timeout=10)
+
+        hook.get_conn()
+
+        mock_client_cls.assert_called_once_with(
+            endpoint=ENDPOINT,
+            credential=mock_default_credential.return_value,
+            api_version="v2",
+            allow_preview=True,
+            connection_timeout=10,
+            read_timeout=10,
+        )
+
+    @mock.patch(f"{MODULE}.get_sync_default_azure_credential", autospec=True)
+    @mock.patch(f"{MODULE}.AIProjectClient", autospec=True)
+    def test_client_is_cached(self, mock_client_cls, mock_default_credential, 
create_mock_connection):
+        create_mock_connection(Connection(conn_id=CONN_ID, 
conn_type="azure_ai_agents", host=ENDPOINT))
+        hook = AzureAIAgentsHook(azure_ai_agents_conn_id=CONN_ID)
+
+        assert hook.get_conn() is hook.get_conn()
+        mock_client_cls.assert_called_once()
+        mock_default_credential.assert_called_once()
+
+    def test_get_endpoint_hook_endpoint_overrides_connection_endpoint(self, 
create_mock_connection):
+        create_mock_connection(Connection(conn_id=CONN_ID, 
conn_type="azure_ai_agents", host=ENDPOINT))
+        endpoint_override = 
"https://override.services.ai.azure.com/api/projects/project";
+        hook = AzureAIAgentsHook(azure_ai_agents_conn_id=CONN_ID, 
endpoint=endpoint_override)
+
+        assert hook._get_endpoint(hook.get_connection(CONN_ID)) == 
endpoint_override
+
+    def test_get_endpoint_raises_when_endpoint_missing(self, 
create_mock_connection):
+        create_mock_connection(Connection(conn_id=CONN_ID, 
conn_type="azure_ai_agents"))
+        hook = AzureAIAgentsHook(azure_ai_agents_conn_id=CONN_ID)
+
+        with pytest.raises(ValueError, match="Azure AI Foundry project 
endpoint must be provided"):
+            hook._get_endpoint(hook.get_connection(CONN_ID))
+
+    def test_create_agent_version(self, hook_with_mocked_client):
+        hook = hook_with_mocked_client
+
+        result = hook.create_agent_version(
+            agent_name=AGENT_NAME,
+            definition=DEFINITION,
+            metadata=METADATA,
+            description=DESCRIPTION,
+            blueprint_reference=BLUEPRINT_REFERENCE,
+        )
+
+        hook.get_conn().agents.create_version.assert_called_once_with(
+            agent_name=AGENT_NAME,
+            definition=DEFINITION,
+            metadata=METADATA,
+            description=DESCRIPTION,
+            blueprint_reference=BLUEPRINT_REFERENCE,
+        )
+        assert result == hook.get_conn().agents.create_version.return_value
+
+    def test_get_agent_version(self, hook_with_mocked_client):
+        hook = hook_with_mocked_client
+
+        result = hook.get_agent_version(agent_name=AGENT_NAME, 
agent_version="1")
+
+        
hook.get_conn().agents.get_version.assert_called_once_with(agent_name=AGENT_NAME,
 agent_version="1")
+        assert result == hook.get_conn().agents.get_version.return_value
+
+    def test_get_agent(self, hook_with_mocked_client):
+        hook = hook_with_mocked_client
+
+        result = hook.get_agent(agent_name=AGENT_NAME)
+
+        
hook.get_conn().agents.get.assert_called_once_with(agent_name=AGENT_NAME)
+        assert result == hook.get_conn().agents.get.return_value
+
+    @pytest.mark.parametrize("force", [False, True])
+    def test_delete_agent(self, hook_with_mocked_client, force):
+        hook = hook_with_mocked_client
+
+        result = hook.delete_agent(agent_name=AGENT_NAME, force=force)
+
+        
hook.get_conn().agents.delete.assert_called_once_with(agent_name=AGENT_NAME, 
force=force)
+        assert result == hook.get_conn().agents.delete.return_value
+
+    @pytest.mark.parametrize("force", [False, True])
+    def test_delete_agent_version(self, hook_with_mocked_client, force):
+        hook = hook_with_mocked_client
+
+        result = hook.delete_agent_version(agent_name=AGENT_NAME, 
agent_version="2", force=force)
+
+        hook.get_conn().agents.delete_version.assert_called_once_with(
+            agent_name=AGENT_NAME, agent_version="2", force=force
+        )
+        assert result == hook.get_conn().agents.delete_version.return_value
+
+    def test_is_agent_version_deleted_when_resource_does_not_exist(self, 
hook_with_mocked_client):
+        hook = hook_with_mocked_client
+        hook.get_conn().agents.get_version.side_effect = 
ResourceNotFoundError("not found")
+
+        assert hook.is_agent_version_deleted(agent_name=AGENT_NAME, 
agent_version="1") is True
+
+    def test_is_agent_version_deleted_when_status_is_deleted(self, 
hook_with_mocked_client):
+        hook = hook_with_mocked_client
+        hook.get_conn().agents.get_version.return_value = {"status": "deleted"}
+
+        assert hook.is_agent_version_deleted(agent_name=AGENT_NAME, 
agent_version="1") is True
+
+    def test_is_agent_version_deleted_when_resource_exists(self, 
hook_with_mocked_client):
+        hook = hook_with_mocked_client
+        hook.get_conn().agents.get_version.return_value = {"status": "active"}
+
+        assert hook.is_agent_version_deleted(agent_name=AGENT_NAME, 
agent_version="1") is False
+
+    def test_is_agent_deleted_when_resource_does_not_exist(self, 
hook_with_mocked_client):
+        hook = hook_with_mocked_client
+        hook.get_conn().agents.get.side_effect = ResourceNotFoundError("not 
found")
+
+        assert hook.is_agent_deleted(agent_name=AGENT_NAME) is True
+
+    def test_is_agent_deleted_when_resource_exists(self, 
hook_with_mocked_client):
+        hook = hook_with_mocked_client
+        hook.get_conn().agents.get.return_value = {"name": AGENT_NAME}
+
+        assert hook.is_agent_deleted(agent_name=AGENT_NAME) is False
+
+    def test_invoke_agent_responses(self, hook_with_mocked_client):
+        hook = hook_with_mocked_client
+        openai_client_manager = hook.get_conn().get_openai_client.return_value
+        openai_client = openai_client_manager.__enter__.return_value
+        openai_client.responses.create.return_value.model_dump.return_value = 
{"output_text": "hello"}
+
+        result = hook.invoke_agent_responses(
+            agent_name=AGENT_NAME,
+            input_data={"input": "hello"},
+            user_isolation_key="user-key",
+        )
+
+        assert result == {"output_text": "hello"}
+        
hook.get_conn().get_openai_client.assert_called_once_with(agent_name=AGENT_NAME)
+        openai_client.responses.create.assert_called_once_with(
+            input="hello",
+            extra_headers={"x-ms-user-isolation-key": "user-key"},
+        )
+        openai_client_manager.__exit__.assert_called_once_with(None, None, 
None)
+
+    def test_invoke_agent_responses_without_isolation_key(self, 
hook_with_mocked_client):
+        hook = hook_with_mocked_client
+        openai_client_manager = hook.get_conn().get_openai_client.return_value
+        openai_client = openai_client_manager.__enter__.return_value
+
+        hook.invoke_agent_responses(agent_name=AGENT_NAME, 
input_data={"input": "hello"})
+
+        openai_client.responses.create.assert_called_once_with(input="hello")
+        openai_client_manager.__exit__.assert_called_once_with(None, None, 
None)
+
+    @mock.patch(f"{MODULE}.HttpRequest", autospec=True)
+    def test_invoke_agent_invocations(self, mock_request_cls, 
hook_with_mocked_client):
+        hook = hook_with_mocked_client
+        response = hook.get_conn().send_request.return_value
+        response.content = b'{"result": "done"}'
+        response.json.return_value = {"result": "done"}
+
+        result = hook.invoke_agent_invocations(
+            agent_name=AGENT_NAME,
+            input_data={"message": "hello"},
+            agent_session_id="session-1",
+            user_isolation_key="user-key",
+        )
+
+        assert result == {"result": "done"}
+        mock_request_cls.assert_called_once_with(
+            "POST",
+            f"/agents/{AGENT_NAME}/endpoint/protocols/invocations",
+            params={"api-version": "v1", "agent_session_id": "session-1"},
+            headers={"x-ms-user-isolation-key": "user-key"},
+            json={"message": "hello"},
+        )
+        
hook.get_conn().send_request.assert_called_once_with(mock_request_cls.return_value)
+        response.raise_for_status.assert_called_once_with()
+
+    @mock.patch(f"{MODULE}.HttpRequest", autospec=True)
+    def test_invoke_agent_invocations_quotes_resource_id(self, 
mock_request_cls, hook_with_mocked_client):
+        hook = hook_with_mocked_client
+        hook.get_conn().send_request.return_value.content = b""
+
+        result = hook.invoke_agent_invocations(
+            agent_name="agent/name with spaces", input_data={"message": 
"hello"}
+        )
+
+        assert result is None
+        assert (
+            mock_request_cls.call_args.args[1]
+            == 
"/agents/agent%2Fname%20with%20spaces/endpoint/protocols/invocations"
+        )
+        assert mock_request_cls.call_args.kwargs["headers"] is None
+
+    @mock.patch(f"{MODULE}.HttpRequest", autospec=True)
+    def test_invoke_agent_invocations_rejects_non_json_response(
+        self, mock_request_cls, hook_with_mocked_client
+    ):
+        hook = hook_with_mocked_client
+        response = hook.get_conn().send_request.return_value
+        response.content = b'{"result": "done"}'
+        response.json.return_value = {"result": object()}
+
+        with pytest.raises(TypeError, match="Cannot serialize.*object.*for 
XCom"):
+            hook.invoke_agent_invocations(agent_name=AGENT_NAME, 
input_data={"message": "hello"})
+
+        response.raise_for_status.assert_called_once_with()
+
+
+class TestAzureAIAgentsAsyncHook:
+    pytestmark = pytest.mark.asyncio
+
+    @mock.patch(f"{MODULE}.get_async_default_azure_credential", autospec=True)
+    @mock.patch(f"{MODULE}.AsyncAIProjectClient", autospec=True)
+    @mock.patch(f"{MODULE}.get_async_connection")
+    async def test_get_async_conn(self, mock_get_connection, mock_client_cls, 
mock_default_credential):
+        mock_get_connection.return_value = Connection(
+            conn_id=CONN_ID, conn_type="azure_ai_agents", host=ENDPOINT
+        )
+        hook = AzureAIAgentsAsyncHook(azure_ai_agents_conn_id=CONN_ID)
+
+        result = await hook.get_async_conn()
+
+        assert result == mock_client_cls.return_value
+        mock_client_cls.assert_called_once_with(
+            endpoint=ENDPOINT,
+            credential=mock_default_credential.return_value,
+            api_version="v1",
+            allow_preview=True,
+            connection_timeout=DEFAULT_REQUEST_TIMEOUT,
+            read_timeout=DEFAULT_REQUEST_TIMEOUT,
+        )
+
+        assert await hook.get_async_conn() is result
+        mock_client_cls.assert_called_once()
+
+    @mock.patch(f"{MODULE}.AsyncClientSecretCredential", autospec=True)
+    @mock.patch(f"{MODULE}.AsyncAIProjectClient", autospec=True)
+    @mock.patch(f"{MODULE}.get_async_connection")
+    async def test_get_async_conn_uses_client_secret_credential(
+        self, mock_get_connection, mock_client_cls, mock_credential_cls
+    ):
+        mock_get_connection.return_value = Connection(
+            conn_id=CONN_ID,
+            conn_type="azure_ai_agents",
+            host=ENDPOINT,
+            login="client-id",
+            password="client-secret",
+            extra={"tenantId": "tenant-id"},
+        )
+        hook = AzureAIAgentsAsyncHook(azure_ai_agents_conn_id=CONN_ID)
+
+        await hook.get_async_conn()
+
+        mock_credential_cls.assert_called_once_with(
+            client_id="client-id",
+            client_secret="client-secret",
+            tenant_id="tenant-id",
+            authority="login.microsoftonline.com",
+        )
+
+    async def 
test_get_async_credential_rejects_partial_service_principal(self):
+        conn = Connection(
+            conn_id=CONN_ID,
+            conn_type="azure_ai_agents",
+            login="client-id",
+        )
+        hook = AzureAIAgentsAsyncHook(azure_ai_agents_conn_id=CONN_ID)
+
+        with pytest.raises(ValueError, match="must all be provided"):
+            hook._get_async_credential(conn)
+
+    async def test_async_get_agent_version(self):
+        hook = AzureAIAgentsAsyncHook(azure_ai_agents_conn_id=CONN_ID)
+        client = mock.MagicMock()
+        client.agents.get_version = mock.AsyncMock(return_value={"version": 
"1"})
+        hook.get_async_conn = mock.AsyncMock(return_value=client)
+
+        result = await hook.async_get_agent_version(agent_name=AGENT_NAME, 
agent_version="1")
+
+        assert result == {"version": "1"}
+        
client.agents.get_version.assert_awaited_once_with(agent_name=AGENT_NAME, 
agent_version="1")
+
+    async def test_close(self):
+        hook = AzureAIAgentsAsyncHook(azure_ai_agents_conn_id=CONN_ID)
+        client = mock.MagicMock(spec=["close"])
+        client.close = mock.AsyncMock()
+        credential = mock.MagicMock(spec=["close"])
+        credential.close = mock.AsyncMock()
+        hook._async_client = client
+        hook._async_credential = credential
+
+        await hook.close()
+
+        client.close.assert_awaited_once_with()
+        credential.close.assert_awaited_once_with()
+        assert hook._async_client is None
+        assert hook._async_credential is None
+
+    async def test_close_closes_credential_when_client_close_fails(self):
+        hook = AzureAIAgentsAsyncHook(azure_ai_agents_conn_id=CONN_ID)
+        client = mock.MagicMock(spec=["close"])
+        client.close = mock.AsyncMock(side_effect=RuntimeError("client close 
failed"))
+        credential = mock.MagicMock(spec=["close"])
+        credential.close = mock.AsyncMock()
+        hook._async_client = client
+        hook._async_credential = credential
+
+        with pytest.raises(RuntimeError, match="client close failed"):
+            await hook.close()
+
+        credential.close.assert_awaited_once_with()
+        assert hook._async_client is None
+        assert hook._async_credential is None
diff --git 
a/providers/microsoft/azure/tests/unit/microsoft/azure/operators/test_ai_agents.py
 
b/providers/microsoft/azure/tests/unit/microsoft/azure/operators/test_ai_agents.py
new file mode 100644
index 00000000000..3612acccb1d
--- /dev/null
+++ 
b/providers/microsoft/azure/tests/unit/microsoft/azure/operators/test_ai_agents.py
@@ -0,0 +1,463 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from unittest import mock
+
+import pytest
+
+from airflow.providers.common.compat.sdk import TaskDeferred
+from airflow.providers.microsoft.azure.hooks.ai_agents import AzureAIAgentsHook
+from airflow.providers.microsoft.azure.operators.ai_agents import (
+    CreateAzureAIAgentOperator,
+    DeleteAzureAIAgentOperator,
+    RunAzureAIAgentOperator,
+    UpdateAzureAIAgentOperator,
+)
+from airflow.providers.microsoft.azure.triggers.ai_agents import 
AzureAIAgentVersionTrigger
+
+MODULE = "airflow.providers.microsoft.azure.operators.ai_agents"
+CONN_ID = "azure_ai_agents_test"
+ENDPOINT = "https://test.services.ai.azure.com/api/projects/test-project";
+AGENT_NAME = "agent-123"
+DEFINITION = {
+    "kind": "hosted",
+    "container_configuration": {"image": "registry.azurecr.io/agent:v1"},
+    "cpu": "1",
+    "memory": "2Gi",
+    "protocol_versions": [{"protocol": "responses", "version": "1.0.0"}],
+}
+METADATA = {"team": "airflow"}
+DESCRIPTION = "Airflow hosted agent"
+BLUEPRINT_REFERENCE = {"type": "ManagedAgentIdentityBlueprint", 
"blueprint_id": "blueprint-1"}
+
+
+class TestCreateAzureAIAgentOperator:
+    @mock.patch.object(AzureAIAgentsHook, "get_agent_version", autospec=True)
+    @mock.patch.object(AzureAIAgentsHook, "create_agent_version", 
autospec=True)
+    def test_execute_waits_until_version_is_active(self, mock_create_version, 
mock_get_version):
+        mock_create_version.return_value = {"name": AGENT_NAME, "version": 
"1", "status": "creating"}
+        mock_get_version.return_value = {"name": AGENT_NAME, "version": "1", 
"status": "active"}
+        operator = CreateAzureAIAgentOperator(
+            task_id="create_agent",
+            agent_name=AGENT_NAME,
+            definition=DEFINITION,
+            metadata=METADATA,
+            description=DESCRIPTION,
+            blueprint_reference=BLUEPRINT_REFERENCE,
+            poll_interval=0,
+            azure_ai_agents_conn_id=CONN_ID,
+            endpoint=ENDPOINT,
+        )
+
+        result = operator.execute(context={})
+
+        assert result == {"name": AGENT_NAME, "version": "1", "status": 
"active"}
+        mock_create_version.assert_called_once_with(
+            mock.ANY,
+            agent_name=AGENT_NAME,
+            definition=DEFINITION,
+            metadata=METADATA,
+            description=DESCRIPTION,
+            blueprint_reference=BLUEPRINT_REFERENCE,
+        )
+        mock_get_version.assert_called_once_with(mock.ANY, 
agent_name=AGENT_NAME, agent_version="1")
+        assert operator.hook.conn_id == CONN_ID
+        assert operator.hook.endpoint == ENDPOINT
+
+    @mock.patch.object(AzureAIAgentsHook, "create_agent_version", 
autospec=True)
+    def test_execute_without_waiting(self, mock_create_version):
+        mock_create_version.return_value = {"name": AGENT_NAME, "version": 
"1", "status": "creating"}
+        operator = CreateAzureAIAgentOperator(
+            task_id="create_agent",
+            agent_name=AGENT_NAME,
+            definition=DEFINITION,
+            wait_for_completion=False,
+        )
+
+        result = operator.execute(context={})
+
+        assert result == {"name": AGENT_NAME, "version": "1", "status": 
"creating"}
+
+    @mock.patch.object(AzureAIAgentsHook, "create_agent_version", 
autospec=True)
+    def test_execute_without_waiting_does_not_require_version(self, 
mock_create_version):
+        mock_create_version.return_value = {"name": AGENT_NAME, "status": 
"creating"}
+        operator = CreateAzureAIAgentOperator(
+            task_id="create_agent",
+            agent_name=AGENT_NAME,
+            definition=DEFINITION,
+            wait_for_completion=False,
+        )
+
+        result = operator.execute(context={})
+
+        assert result == {"name": AGENT_NAME, "status": "creating"}
+
+    @mock.patch.object(AzureAIAgentsHook, "create_agent_version", 
autospec=True)
+    def test_execute_raises_when_wait_response_has_no_version(self, 
mock_create_version):
+        mock_create_version.return_value = {"name": AGENT_NAME, "status": 
"creating"}
+        operator = CreateAzureAIAgentOperator(
+            task_id="create_agent",
+            agent_name=AGENT_NAME,
+            definition=DEFINITION,
+        )
+
+        with pytest.raises(ValueError, match="did not include a version"):
+            operator.execute(context={})
+
+    @mock.patch.object(AzureAIAgentsHook, "get_agent_version", autospec=True)
+    @mock.patch.object(AzureAIAgentsHook, "create_agent_version", 
autospec=True)
+    def test_execute_raises_when_version_fails(self, mock_create_version, 
mock_get_version):
+        mock_create_version.return_value = {"name": AGENT_NAME, "version": 
"1", "status": "creating"}
+        mock_get_version.return_value = {"name": AGENT_NAME, "version": "1", 
"status": "failed"}
+        operator = CreateAzureAIAgentOperator(
+            task_id="create_agent", agent_name=AGENT_NAME, 
definition=DEFINITION
+        )
+
+        with pytest.raises(
+            RuntimeError,
+            match=f"Azure AI Hosted agent {AGENT_NAME} version 1 failed: No 
error details were returned.",
+        ):
+            operator.execute(context={})
+
+    @mock.patch.object(AzureAIAgentsHook, "get_agent_version", autospec=True)
+    @mock.patch.object(AzureAIAgentsHook, "create_agent_version", 
autospec=True)
+    def test_execute_raises_when_version_reaches_unknown_status(self, 
mock_create_version, mock_get_version):
+        mock_create_version.return_value = {"name": AGENT_NAME, "version": 
"1", "status": "creating"}
+        mock_get_version.return_value = {"name": AGENT_NAME, "version": "1", 
"status": "paused"}
+        operator = CreateAzureAIAgentOperator(
+            task_id="create_agent", agent_name=AGENT_NAME, 
definition=DEFINITION
+        )
+
+        with pytest.raises(RuntimeError, match="unknown status paused"):
+            operator.execute(context={})
+
+    @mock.patch(f"{MODULE}.time.sleep", autospec=True)
+    @mock.patch(f"{MODULE}.time.monotonic", autospec=True, side_effect=[0, 0])
+    @mock.patch.object(AzureAIAgentsHook, "get_agent_version", autospec=True)
+    @mock.patch.object(AzureAIAgentsHook, "create_agent_version", 
autospec=True)
+    def test_execute_raises_when_version_times_out(
+        self, mock_create_version, mock_get_version, mock_monotonic, mock_sleep
+    ):
+        mock_create_version.return_value = {"name": AGENT_NAME, "version": 
"1", "status": "creating"}
+        mock_get_version.return_value = {"name": AGENT_NAME, "version": "1", 
"status": "creating"}
+        operator = CreateAzureAIAgentOperator(
+            task_id="create_agent",
+            agent_name=AGENT_NAME,
+            definition=DEFINITION,
+            timeout=0,
+            poll_interval=0,
+        )
+
+        with pytest.raises(TimeoutError, match="Timeout waiting for Azure AI 
Hosted agent"):
+            operator.execute(context={})
+
+        mock_sleep.assert_not_called()
+
+    @mock.patch.object(AzureAIAgentsHook, "create_agent_version", 
autospec=True)
+    def test_execute_defers(self, mock_create_version):
+        mock_create_version.return_value = {"name": AGENT_NAME, "version": 
"1", "status": "creating"}
+        operator = CreateAzureAIAgentOperator(
+            task_id="create_agent",
+            agent_name=AGENT_NAME,
+            definition=DEFINITION,
+            deferrable=True,
+            timeout=10,
+            poll_interval=2,
+            azure_ai_agents_conn_id=CONN_ID,
+            endpoint=ENDPOINT,
+            api_version="v2",
+        )
+
+        with pytest.raises(TaskDeferred) as exc:
+            operator.execute(context={})
+
+        trigger = exc.value.trigger
+        assert isinstance(trigger, AzureAIAgentVersionTrigger)
+        assert trigger.azure_ai_agents_conn_id == CONN_ID
+        assert trigger.endpoint == ENDPOINT
+        assert trigger.api_version == "v2"
+        assert trigger.agent_name == AGENT_NAME
+        assert trigger.agent_version == "1"
+        assert trigger.timeout == 10
+        assert trigger.poll_interval == 2
+
+    def test_execute_complete_success(self):
+        operator = CreateAzureAIAgentOperator(
+            task_id="create_agent",
+            agent_name=AGENT_NAME,
+            definition=DEFINITION,
+        )
+
+        result = operator.execute_complete(
+            context={},
+            event={
+                "status": "success",
+                "message": "active",
+                "version": {"name": AGENT_NAME, "version": "1", "status": 
"active"},
+            },
+        )
+
+        assert result == {"name": AGENT_NAME, "version": "1", "status": 
"active"}
+
+    def test_execute_complete_requires_version_payload(self):
+        operator = CreateAzureAIAgentOperator(
+            task_id="create_agent",
+            agent_name=AGENT_NAME,
+            definition=DEFINITION,
+        )
+
+        with pytest.raises(RuntimeError, match="did not include version 
payload"):
+            operator.execute_complete(context={}, event={"status": "success", 
"message": "active"})
+
+
+class TestUpdateAzureAIAgentOperator:
+    @mock.patch.object(AzureAIAgentsHook, "get_agent_version", autospec=True)
+    @mock.patch.object(AzureAIAgentsHook, "create_agent_version", 
autospec=True)
+    def test_execute_creates_new_version(self, mock_create_version, 
mock_get_version):
+        mock_create_version.return_value = {"name": AGENT_NAME, "version": 
"2", "status": "creating"}
+        mock_get_version.return_value = {"name": AGENT_NAME, "version": "2", 
"status": "active"}
+        operator = UpdateAzureAIAgentOperator(
+            task_id="update_agent",
+            agent_name=AGENT_NAME,
+            definition=DEFINITION,
+            poll_interval=0,
+        )
+
+        result = operator.execute(context={})
+
+        assert result == {"name": AGENT_NAME, "version": "2", "status": 
"active"}
+        mock_create_version.assert_called_once_with(
+            mock.ANY,
+            agent_name=AGENT_NAME,
+            definition=DEFINITION,
+            metadata=None,
+            description=None,
+            blueprint_reference=None,
+        )
+        mock_get_version.assert_called_once_with(mock.ANY, 
agent_name=AGENT_NAME, agent_version="2")
+
+    @mock.patch.object(AzureAIAgentsHook, "create_agent_version", 
autospec=True)
+    def test_execute_defers(self, mock_create_version):
+        mock_create_version.return_value = {"name": AGENT_NAME, "version": 
"2", "status": "creating"}
+        operator = UpdateAzureAIAgentOperator(
+            task_id="update_agent",
+            agent_name=AGENT_NAME,
+            definition=DEFINITION,
+            deferrable=True,
+            timeout=10,
+            poll_interval=2,
+        )
+
+        with pytest.raises(TaskDeferred) as exc:
+            operator.execute(context={})
+
+        trigger = exc.value.trigger
+        assert isinstance(trigger, AzureAIAgentVersionTrigger)
+        assert trigger.agent_version == "2"
+
+
+class TestRunAzureAIAgentOperator:
+    @mock.patch.object(AzureAIAgentsHook, "invoke_agent_responses", 
autospec=True)
+    def test_execute_responses_protocol(self, mock_invoke):
+        mock_invoke.return_value = {"output_text": "hello"}
+        operator = RunAzureAIAgentOperator(
+            task_id="run_agent",
+            agent_name=AGENT_NAME,
+            protocol="responses",
+            input_data={"input": "hello"},
+            user_isolation_key="user-key",
+        )
+
+        result = operator.execute(context={})
+
+        assert result == {"output_text": "hello"}
+        mock_invoke.assert_called_once_with(
+            mock.ANY,
+            agent_name=AGENT_NAME,
+            input_data={"input": "hello"},
+            user_isolation_key="user-key",
+        )
+
+    @mock.patch.object(AzureAIAgentsHook, "invoke_agent_invocations", 
autospec=True)
+    def test_execute_invocations_protocol(self, mock_invoke):
+        mock_invoke.return_value = {"result": "done"}
+        operator = RunAzureAIAgentOperator(
+            task_id="run_agent",
+            agent_name=AGENT_NAME,
+            protocol="invocations",
+            input_data={"message": "hello"},
+            agent_session_id="session-1",
+            user_isolation_key="user-key",
+        )
+
+        result = operator.execute(context={})
+
+        assert result == {"result": "done"}
+        mock_invoke.assert_called_once_with(
+            mock.ANY,
+            agent_name=AGENT_NAME,
+            input_data={"message": "hello"},
+            agent_session_id="session-1",
+            user_isolation_key="user-key",
+        )
+
+    def test_execute_raises_for_unknown_protocol(self):
+        operator = RunAzureAIAgentOperator(
+            task_id="run_agent",
+            agent_name=AGENT_NAME,
+            protocol="unknown",
+            input_data={"message": "hello"},
+        )
+
+        with pytest.raises(ValueError, match="protocol must be either"):
+            operator.execute(context={})
+
+
+class TestDeleteAzureAIAgentOperator:
+    @mock.patch.object(AzureAIAgentsHook, "is_agent_deleted", autospec=True)
+    @mock.patch.object(AzureAIAgentsHook, "delete_agent", autospec=True)
+    def test_execute_deletes_agent(self, mock_delete_agent, mock_is_deleted):
+        mock_delete_agent.return_value = {"object": "agent.deleted", "name": 
AGENT_NAME, "deleted": True}
+        mock_is_deleted.return_value = True
+        operator = DeleteAzureAIAgentOperator(
+            task_id="delete_agent",
+            agent_name=AGENT_NAME,
+            poll_interval=0,
+        )
+
+        with mock.patch.object(operator.log, "info") as mock_log_info:
+            result = operator.execute(context={})
+
+        assert result == {"object": "agent.deleted", "name": AGENT_NAME, 
"deleted": True}
+        mock_delete_agent.assert_called_once_with(mock.ANY, 
agent_name=AGENT_NAME, force=False)
+        mock_is_deleted.assert_called_once_with(mock.ANY, 
agent_name=AGENT_NAME)
+        mock_log_info.assert_any_call("Azure AI Hosted %s was deleted.", 
f"agent {AGENT_NAME}")
+
+    @mock.patch.object(AzureAIAgentsHook, "delete_agent", autospec=True)
+    def test_execute_deletes_agent_with_force(self, mock_delete_agent):
+        mock_delete_agent.return_value = {"object": "agent.deleted", "name": 
AGENT_NAME, "deleted": True}
+        operator = DeleteAzureAIAgentOperator(
+            task_id="delete_agent",
+            agent_name=AGENT_NAME,
+            force=True,
+            wait_for_completion=False,
+        )
+
+        result = operator.execute(context={})
+
+        assert result == {"object": "agent.deleted", "name": AGENT_NAME, 
"deleted": True}
+        mock_delete_agent.assert_called_once_with(mock.ANY, 
agent_name=AGENT_NAME, force=True)
+
+    @mock.patch.object(AzureAIAgentsHook, "is_agent_version_deleted", 
autospec=True)
+    @mock.patch.object(AzureAIAgentsHook, "delete_agent_version", 
autospec=True)
+    def test_execute_deletes_agent_version(self, mock_delete_version, 
mock_is_deleted):
+        mock_delete_version.return_value = {"object": "agent.version.deleted", 
"deleted": True}
+        mock_is_deleted.return_value = True
+        operator = DeleteAzureAIAgentOperator(
+            task_id="delete_agent",
+            agent_name=AGENT_NAME,
+            agent_version="2",
+            poll_interval=0,
+        )
+
+        with mock.patch.object(operator.log, "info") as mock_log_info:
+            result = operator.execute(context={})
+
+        assert result == {"object": "agent.version.deleted", "deleted": True}
+        mock_delete_version.assert_called_once_with(
+            mock.ANY, agent_name=AGENT_NAME, agent_version="2", force=False
+        )
+        mock_is_deleted.assert_called_once_with(mock.ANY, 
agent_name=AGENT_NAME, agent_version="2")
+        mock_log_info.assert_any_call("Azure AI Hosted %s was deleted.", 
f"agent {AGENT_NAME} version 2")
+
+    @mock.patch.object(AzureAIAgentsHook, "delete_agent_version", 
autospec=True)
+    def test_execute_deletes_agent_version_with_force(self, 
mock_delete_version):
+        mock_delete_version.return_value = {"object": "agent.version.deleted", 
"deleted": True}
+        operator = DeleteAzureAIAgentOperator(
+            task_id="delete_agent_version",
+            agent_name=AGENT_NAME,
+            agent_version="2",
+            force=True,
+            wait_for_completion=False,
+        )
+
+        result = operator.execute(context={})
+
+        assert result == {"object": "agent.version.deleted", "deleted": True}
+        mock_delete_version.assert_called_once_with(
+            mock.ANY, agent_name=AGENT_NAME, agent_version="2", force=True
+        )
+
+    @mock.patch(f"{MODULE}.time.sleep", autospec=True)
+    @mock.patch.object(AzureAIAgentsHook, "is_agent_deleted", autospec=True)
+    @mock.patch.object(AzureAIAgentsHook, "delete_agent", autospec=True)
+    def test_execute_polls_until_agent_is_deleted(self, mock_delete_agent, 
mock_is_deleted, mock_sleep):
+        mock_delete_agent.return_value = {"object": "agent.deleted", "name": 
AGENT_NAME, "deleted": True}
+        mock_is_deleted.side_effect = [False, False, True]
+        operator = DeleteAzureAIAgentOperator(
+            task_id="delete_agent",
+            agent_name=AGENT_NAME,
+            poll_interval=1,
+        )
+
+        operator.execute(context={})
+
+        assert mock_is_deleted.call_count == 3
+        assert mock_sleep.call_count == 2
+        mock_sleep.assert_called_with(1)
+
+    @mock.patch(f"{MODULE}.time.sleep", autospec=True)
+    @mock.patch(f"{MODULE}.time.monotonic", autospec=True, side_effect=[0, 0])
+    @mock.patch.object(AzureAIAgentsHook, "is_agent_deleted", autospec=True)
+    @mock.patch.object(AzureAIAgentsHook, "delete_agent", autospec=True)
+    def test_execute_raises_when_deletion_times_out(
+        self, mock_delete_agent, mock_is_deleted, mock_monotonic, mock_sleep
+    ):
+        mock_is_deleted.return_value = False
+        operator = DeleteAzureAIAgentOperator(
+            task_id="delete_agent",
+            agent_name=AGENT_NAME,
+            timeout=0,
+            poll_interval=0,
+        )
+
+        with pytest.raises(TimeoutError, match="Timeout waiting for Azure AI 
Hosted agent"):
+            operator.execute(context={})
+
+        mock_sleep.assert_not_called()
+
+
[email protected](
+    ("event", "exception", "message"),
+    [
+        (None, RuntimeError, "Trigger returned no event."),
+        ({"status": "timeout", "message": "timed out"}, TimeoutError, "timed 
out"),
+        ({"status": "error", "message": "failed"}, RuntimeError, "failed"),
+        ({"status": "unknown"}, ValueError, "Unexpected trigger event status"),
+    ],
+)
+def test_execute_complete_event_errors(event, exception, message):
+    operator = CreateAzureAIAgentOperator(
+        task_id="create_agent",
+        agent_name=AGENT_NAME,
+        definition=DEFINITION,
+    )
+
+    with pytest.raises(exception, match=message):
+        operator.execute_complete(context={}, event=event)
diff --git 
a/providers/microsoft/azure/tests/unit/microsoft/azure/test__ai_agents.py 
b/providers/microsoft/azure/tests/unit/microsoft/azure/test__ai_agents.py
new file mode 100644
index 00000000000..d20a2859490
--- /dev/null
+++ b/providers/microsoft/azure/tests/unit/microsoft/azure/test__ai_agents.py
@@ -0,0 +1,77 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+import pytest
+
+from airflow.providers.microsoft.azure._ai_agents import (
+    _get_agent_version,
+    _get_version_status,
+    _serialize_resource,
+)
+
+AGENT_NAME = "agent-123"
+
+
+def test_get_version_status_raises_when_status_missing():
+    with pytest.raises(ValueError, match="did not include a status"):
+        _get_version_status({})
+
+
+def test_get_version_status_normalizes_enum_values():
+    class FakeEnum:
+        value = "Active"
+
+    assert _get_version_status({"status": FakeEnum()}) == "active"
+
+
+def test_get_agent_version_raises_when_version_missing():
+    with pytest.raises(ValueError, match="did not include a version"):
+        _get_agent_version({})
+
+
+def test_serialize_resource_uses_as_dict_serializer():
+    class SdkModel:
+        def as_dict(self):
+            return {"name": AGENT_NAME, "versions": [{"version": "1"}]}
+
+    assert _serialize_resource(SdkModel()) == {"name": AGENT_NAME, "versions": 
[{"version": "1"}]}
+
+
+def test_serialize_resource_uses_model_dump_serializer():
+    class PydanticModel:
+        def model_dump(self):
+            return {"output_text": "hello"}
+
+    assert _serialize_resource(PydanticModel()) == {"output_text": "hello"}
+
+
+def test_serialize_resource_rejects_arbitrary_objects():
+    class ArbitraryObject:
+        value = "class-value"
+
+        def __init__(self):
+            self.value = "instance-value"
+
+    with pytest.raises(TypeError, match="Cannot 
serialize.*ArbitraryObject.*for XCom"):
+        _serialize_resource(ArbitraryObject())
+
+
+def test_serialize_resource_rejects_non_string_mapping_keys():
+    with pytest.raises(TypeError, match="must use string keys"):
+        _serialize_resource({1: "value"})
diff --git 
a/providers/microsoft/azure/tests/unit/microsoft/azure/triggers/test_ai_agents.py
 
b/providers/microsoft/azure/tests/unit/microsoft/azure/triggers/test_ai_agents.py
new file mode 100644
index 00000000000..5db1e9c152b
--- /dev/null
+++ 
b/providers/microsoft/azure/tests/unit/microsoft/azure/triggers/test_ai_agents.py
@@ -0,0 +1,197 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from unittest import mock
+
+import pytest
+
+from airflow.providers.microsoft.azure.hooks.ai_agents import 
AzureAIAgentsAsyncHook
+from airflow.providers.microsoft.azure.triggers.ai_agents import 
AzureAIAgentVersionTrigger
+
+MODULE = "airflow.providers.microsoft.azure.triggers.ai_agents"
+CONN_ID = "azure_ai_agents_test"
+ENDPOINT = "https://test.services.ai.azure.com/api/projects/test-project";
+AGENT_NAME = "agent-123"
+
+
+def build_trigger(**overrides):
+    kwargs = {
+        "azure_ai_agents_conn_id": CONN_ID,
+        "endpoint": ENDPOINT,
+        "api_version": "v1",
+        "agent_name": AGENT_NAME,
+        "agent_version": "1",
+        "timeout": 10,
+        "poll_interval": 2,
+        **overrides,
+    }
+    return AzureAIAgentVersionTrigger(**kwargs)
+
+
+async def get_trigger_event(trigger):
+    generator = trigger.run()
+    event = await anext(generator)
+    await generator.aclose()
+    return event
+
+
+class TestAzureAIAgentVersionTrigger:
+    def test_serialize(self):
+        trigger = build_trigger(api_version="v2")
+
+        classpath, kwargs = trigger.serialize()
+
+        assert classpath == 
"airflow.providers.microsoft.azure.triggers.ai_agents.AzureAIAgentVersionTrigger"
+        assert kwargs == {
+            "azure_ai_agents_conn_id": CONN_ID,
+            "endpoint": ENDPOINT,
+            "api_version": "v2",
+            "agent_name": AGENT_NAME,
+            "agent_version": "1",
+            "timeout": 10,
+            "poll_interval": 2,
+        }
+
+    def test_serialize_default_endpoint(self):
+        trigger = AzureAIAgentVersionTrigger(
+            azure_ai_agents_conn_id=CONN_ID,
+            api_version="v1",
+            agent_name=AGENT_NAME,
+            agent_version="1",
+            timeout=10,
+            poll_interval=2,
+        )
+
+        _, kwargs = trigger.serialize()
+
+        assert kwargs["endpoint"] is None
+
+    @mock.patch(f"{MODULE}._serialize_resource", autospec=True)
+    def test_build_trigger_event_does_not_serialize_intermediate_version(self, 
mock_serialize):
+        trigger = build_trigger()
+
+        assert trigger._build_trigger_event({"status": "creating"}) is None
+        mock_serialize.assert_not_called()
+
+    @pytest.mark.asyncio
+    @mock.patch.object(AzureAIAgentsAsyncHook, "close", autospec=True)
+    @mock.patch.object(AzureAIAgentsAsyncHook, "async_get_agent_version", 
autospec=True)
+    async def test_run_success(self, mock_get_version, mock_close):
+        mock_get_version.return_value = {"name": AGENT_NAME, "version": "1", 
"status": "active"}
+        trigger = build_trigger()
+
+        event = await get_trigger_event(trigger)
+
+        assert event.payload == {
+            "status": "success",
+            "message": f"Azure AI Hosted agent {AGENT_NAME} version 1 is 
active.",
+            "version": {"name": AGENT_NAME, "version": "1", "status": 
"active"},
+        }
+        mock_close.assert_awaited_once()
+
+    @pytest.mark.asyncio
+    @mock.patch(f"{MODULE}.asyncio.sleep", autospec=True)
+    @mock.patch.object(AzureAIAgentsAsyncHook, "async_get_agent_version", 
autospec=True)
+    async def test_run_polls_until_success(self, mock_get_version, mock_sleep):
+        creating = {"name": AGENT_NAME, "version": "1", "status": "creating"}
+        active = {"name": AGENT_NAME, "version": "1", "status": "active"}
+        mock_get_version.side_effect = [creating, creating, active]
+        trigger = build_trigger(poll_interval=1)
+
+        event = await get_trigger_event(trigger)
+
+        assert event.payload["status"] == "success"
+        assert mock_get_version.call_count == 3
+        assert mock_sleep.call_count == 2
+        mock_sleep.assert_awaited_with(1)
+
+    @pytest.mark.asyncio
+    @mock.patch.object(AzureAIAgentsAsyncHook, "async_get_agent_version", 
autospec=True)
+    async def test_run_failure(self, mock_get_version):
+        mock_get_version.return_value = {
+            "name": AGENT_NAME,
+            "version": "1",
+            "status": "failed",
+            "error": {"message": "boom"},
+        }
+        trigger = build_trigger()
+
+        event = await get_trigger_event(trigger)
+
+        assert event.payload["status"] == "error"
+        assert "boom" in event.payload["message"]
+        assert event.payload["version"]["status"] == "failed"
+
+    @pytest.mark.asyncio
+    @mock.patch.object(AzureAIAgentsAsyncHook, "async_get_agent_version", 
autospec=True)
+    async def test_run_failure_without_error_details(self, mock_get_version):
+        mock_get_version.return_value = {"name": AGENT_NAME, "version": "1", 
"status": "failed"}
+        trigger = build_trigger()
+
+        event = await get_trigger_event(trigger)
+
+        assert event.payload["status"] == "error"
+        assert event.payload["message"] == (
+            f"Azure AI Hosted agent {AGENT_NAME} version 1 failed: No error 
details were returned."
+        )
+        assert event.payload["version"]["status"] == "failed"
+
+    @pytest.mark.asyncio
+    @mock.patch.object(AzureAIAgentsAsyncHook, "async_get_agent_version", 
autospec=True)
+    async def test_run_unknown_status(self, mock_get_version):
+        mock_get_version.return_value = {"name": AGENT_NAME, "version": "1", 
"status": "paused"}
+        trigger = build_trigger()
+
+        event = await get_trigger_event(trigger)
+
+        assert event.payload["status"] == "error"
+        assert "unknown status paused" in event.payload["message"]
+
+    @pytest.mark.asyncio
+    @mock.patch(f"{MODULE}.asyncio.sleep", autospec=True)
+    @mock.patch(f"{MODULE}.time.monotonic", autospec=True, side_effect=[0, 0])
+    @mock.patch.object(AzureAIAgentsAsyncHook, "async_get_agent_version", 
autospec=True)
+    async def test_run_timeout(self, mock_get_version, mock_monotonic, 
mock_sleep):
+        mock_get_version.return_value = {"name": AGENT_NAME, "version": "1", 
"status": "creating"}
+        trigger = build_trigger(timeout=0)
+
+        event = await get_trigger_event(trigger)
+
+        assert event.payload == {
+            "status": "timeout",
+            "message": f"Timeout waiting for Azure AI Hosted agent 
{AGENT_NAME} version 1.",
+            "version": {"name": AGENT_NAME, "version": "1"},
+        }
+        mock_sleep.assert_not_called()
+
+    @pytest.mark.asyncio
+    @mock.patch.object(AzureAIAgentsAsyncHook, "close", autospec=True)
+    @mock.patch.object(AzureAIAgentsAsyncHook, "async_get_agent_version", 
autospec=True)
+    async def test_run_exception(self, mock_get_version, mock_close):
+        mock_get_version.side_effect = RuntimeError("boom")
+        trigger = build_trigger()
+
+        event = await get_trigger_event(trigger)
+
+        assert event.payload == {
+            "status": "error",
+            "message": f"Failed while polling Azure AI Hosted agent 
{AGENT_NAME} version 1: boom",
+            "version": {"name": AGENT_NAME, "version": "1"},
+        }
+        mock_close.assert_awaited_once()
diff --git a/uv.lock b/uv.lock
index 7f8c32196b5..2b08c898b6c 100644
--- a/uv.lock
+++ b/uv.lock
@@ -6225,8 +6225,10 @@ version = "13.5.1"
 source = { editable = "providers/microsoft/azure" }
 dependencies = [
     { name = "adlfs" },
+    { name = "aiohttp" },
     { name = "apache-airflow" },
     { name = "apache-airflow-providers-common-compat" },
+    { name = "azure-ai-projects" },
     { name = "azure-batch" },
     { name = "azure-cosmos" },
     { name = "azure-datalake-store" },
@@ -6299,6 +6301,7 @@ docs = [
 [package.metadata]
 requires-dist = [
     { name = "adlfs", specifier = ">=2023.10.0" },
+    { name = "aiohttp", specifier = ">=3.14.0" },
     { name = "apache-airflow", editable = "." },
     { name = "apache-airflow-providers-amazon", marker = "extra == 'amazon'", 
editable = "providers/amazon" },
     { name = "apache-airflow-providers-common-compat", editable = 
"providers/common/compat" },
@@ -6307,6 +6310,7 @@ requires-dist = [
     { name = "apache-airflow-providers-openlineage", marker = "extra == 
'openlineage'", editable = "providers/openlineage" },
     { name = "apache-airflow-providers-oracle", marker = "extra == 'oracle'", 
editable = "providers/oracle" },
     { name = "apache-airflow-providers-sftp", marker = "extra == 'sftp'", 
editable = "providers/sftp" },
+    { name = "azure-ai-projects", specifier = ">=2.2.0" },
     { name = "azure-batch", specifier = ">=8.0.0,<15.0.0" },
     { name = "azure-cosmos", specifier = ">=4.6.0" },
     { name = "azure-datalake-store", specifier = ">=0.0.45" },
@@ -9478,6 +9482,23 @@ wheels = [
     { url = 
"https://files.pythonhosted.org/packages/ef/c3/f30a7a63e664acc7c2545ca0491b6ce8264536e0e5cad3965f1d1b91e960/aws_xray_sdk-2.15.0-py2.py3-none-any.whl";,
 hash = 
"sha256:422d62ad7d52e373eebb90b642eb1bb24657afe03b22a8df4a8b2e5108e278a3", size 
= 103228, upload-time = "2025-10-29T21:00:24.12Z" },
 ]
 
+[[package]]
+name = "azure-ai-projects"
+version = "2.2.0"
+source = { registry = "https://pypi.org/simple"; }
+dependencies = [
+    { name = "azure-core" },
+    { name = "azure-identity" },
+    { name = "azure-storage-blob" },
+    { name = "isodate" },
+    { name = "openai" },
+    { name = "typing-extensions" },
+]
+sdist = { url = 
"https://files.pythonhosted.org/packages/b9/d8/24342aea74fe75b0a8378b6eff665b9c1cb63f855c1a96f70a0095e474a2/azure_ai_projects-2.2.0.tar.gz";,
 hash = 
"sha256:58ee31bb031cfb004051145c545294bb0d32de679c670c312ef384845bd72cef", size 
= 668496, upload-time = "2026-05-30T00:20:59.099Z" }
+wheels = [
+    { url = 
"https://files.pythonhosted.org/packages/40/cf/90f27a2b48c9b748f84194b07e565f900e7f0ce0500da9b9f067dca599d3/azure_ai_projects-2.2.0-py3-none-any.whl";,
 hash = 
"sha256:8f89bdaca4df1bd479d3bd2bd0f19a0905d60be6d17b84a69e8fabd82eac5906", size 
= 344307, upload-time = "2026-05-30T00:21:00.672Z" },
+]
+
 [[package]]
 name = "azure-batch"
 version = "14.2.0"

Reply via email to