Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .activity import Activity
from .agents_model import AgentsModel
from ._type_aliases import NonEmptyString
from .conversation_account import ConversationAccount


class ConversationParameters(AgentsModel):
Expand All @@ -29,6 +30,8 @@ class ConversationParameters(AgentsModel):
:type channel_data: object
:param tenant_id: (Optional) The tenant ID in which the conversation should be created
:type tenant_id: str
:param conversation: (Optional) The conversation account to use when creating the new conversation
:type conversation: ~microsoft_agents.activity.ConversationAccount
"""

is_group: bool = None
Expand All @@ -38,3 +41,4 @@ class ConversationParameters(AgentsModel):
activity: Activity = None
channel_data: object = None
tenant_id: NonEmptyString = None
conversation: ConversationAccount = None
Comment thread
rodrigobr-msft marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
from typing import Optional, Callable, Awaitable, cast
from dataclasses import dataclass

from microsoft_agents.activity import Activity, Channels, SignInConstants, TokenResponse
from microsoft_agents.activity import (
Activity,
Channels,
ChannelId,
SignInConstants,
TokenResponse,
)
from microsoft_agents.activity.activity_types import ActivityTypes

from ...turn_context import TurnContext
Expand Down Expand Up @@ -283,7 +289,8 @@ async def _start_or_continue_sign_in(
elif sign_in_response.tag in [_FlowStateTag.BEGIN, _FlowStateTag.CONTINUE]:
# Handling special case for Teams SSO, ConsentRequired
if not (
context.activity.channel_id.channel == Channels.ms_teams
ChannelId.get_channel(context.activity.channel_id)
== Channels.ms_teams.value
and sign_in_state.continuation_activity
and context.activity.type == ActivityTypes.invoke
and context.activity.name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from __future__ import annotations

from typing import Optional, TYPE_CHECKING
from typing import TYPE_CHECKING

from microsoft_agents.activity import ConversationReference
from microsoft_agents.hosting.core.authorization import ClaimsIdentity
Expand Down Expand Up @@ -39,7 +39,7 @@ class Conversation(StoreItem):

def __init__(
self,
claims: "dict[str, str] | ClaimsIdentity",
claims: dict[str, str] | ClaimsIdentity,
conversation_reference: ConversationReference,
) -> None:
if isinstance(claims, ClaimsIdentity):
Expand All @@ -55,7 +55,7 @@ def __init__(
# ------------------------------------------------------------------

@classmethod
def from_turn_context(cls, context: "TurnContext") -> "Conversation":
def from_turn_context(cls, context: TurnContext) -> Conversation:
"""
Create a :class:`microsoft_agents.hosting.core.app.proactive.Conversation` from the current turn context.

Expand All @@ -65,11 +65,7 @@ def from_turn_context(cls, context: "TurnContext") -> "Conversation":
and conversation reference.
:rtype: :class:`microsoft_agents.hosting.core.app.proactive.Conversation`
"""
from microsoft_agents.hosting.core.channel_adapter import ChannelAdapter

identity: Optional[ClaimsIdentity] = context.turn_state.get(
ChannelAdapter.AGENT_IDENTITY_KEY
)
identity: ClaimsIdentity | None = context.identity
reference = context.activity.get_conversation_reference()
return cls(identity or {}, reference)

Expand All @@ -78,7 +74,7 @@ def from_turn_context(cls, context: "TurnContext") -> "Conversation":
# ------------------------------------------------------------------

@staticmethod
def claims_from_identity(identity: ClaimsIdentity) -> "dict[str, str]":
def claims_from_identity(identity: ClaimsIdentity) -> dict[str, str]:
"""
Return the subset of claims from *identity* that are relevant for proactive
messaging (``aud``, ``azp``, ``appid``, ``idtyp``, ``ver``, ``iss``, ``tid``).
Expand All @@ -91,7 +87,7 @@ def claims_from_identity(identity: ClaimsIdentity) -> "dict[str, str]":
return {k: v for k, v in identity.claims.items() if k in _PERSISTED_CLAIM_KEYS}

@staticmethod
def identity_from_claims(claims: "dict[str, str]") -> ClaimsIdentity:
def identity_from_claims(claims: dict[str, str]) -> ClaimsIdentity:
"""
Reconstruct a :class:`~microsoft_agents.hosting.core.authorization.ClaimsIdentity`
from a previously persisted claims dict.
Expand Down Expand Up @@ -142,7 +138,7 @@ def store_item_to_json(self) -> dict:
}

@staticmethod
def from_json_to_store_item(json_data: dict) -> "Conversation":
def from_json_to_store_item(json_data: dict) -> Conversation:
reference = ConversationReference.model_validate(
json_data.get("conversation_reference", {})
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

from __future__ import annotations

from abc import ABC, abstractmethod
from collections.abc import Callable
from typing import Awaitable
from microsoft_agents.hosting.core.authorization import ClaimsIdentity
from microsoft_agents.activity import ChannelAdapterProtocol
from microsoft_agents.activity import (
Activity,
ChannelId,
ConversationAccount,
ConversationReference,
ConversationParameters,
ResourceResponse,
)

from .turn_context import TurnContext
from .middleware_set import MiddlewareSet
from .middleware_set import MiddlewareSet, Middleware


class ChannelAdapter(ABC, ChannelAdapterProtocol):
Expand Down Expand Up @@ -78,7 +81,7 @@ async def delete_activity(
"""
raise NotImplementedError()

def use(self, middleware):
def use(self, middleware: Middleware) -> ChannelAdapter:
"""
Registers a middleware handler with the adapter.

Expand Down Expand Up @@ -202,11 +205,11 @@ async def create_conversation(

# Create a conversation update activity
conversation_update = Activity(
type=ActivityTypes.CONVERSATION_UPDATE,
channel_id=channel_id,
type=ActivityTypes.conversation_update,
channel_id=ChannelId(channel_id),
service_url=service_url,
conversation=conversation_parameters.conversation,
recipient=conversation_parameters.bot,
recipient=conversation_parameters.agent,
from_property=conversation_parameters.members[0],
members_added=conversation_parameters.members,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
from __future__ import annotations

from abc import ABC
from copy import Error
from http import HTTPStatus
from typing import Awaitable, Callable, cast, Optional
from typing import Awaitable, Callable, cast
from uuid import uuid4

from microsoft_agents.activity import (
Expand Down Expand Up @@ -65,16 +64,10 @@ async def send_activities(
:type activities: list[:class:`microsoft_agents.activity.Activity`]
:return: List of resource responses for the sent activities.
:rtype: list[:class:`microsoft_agents.activity.ResourceResponse`]
:raises TypeError: If context or activities are None/invalid.
:raises ValueError: If the activities list is empty.
"""
if not context:
raise TypeError("Expected TurnContext but got None instead")

if activities is None:
raise TypeError("Expected Activities list but got None instead")

if len(activities) == 0:
raise TypeError("Expecting one or more activities, but the list was empty.")
raise ValueError("send_activities: activities list cannot be empty")

responses = []

Expand All @@ -97,7 +90,9 @@ async def send_activities(
context.turn_state.get(self._AGENT_CONNECTOR_CLIENT_KEY),
)
if not connector_client:
raise Error("Unable to extract ConnectorClient from turn context.")
raise RuntimeError(
"Unable to extract ConnectorClient from turn context."
)

with spans.AdapterSendActivities([activity]):
if activity.reply_to_id:
Expand Down Expand Up @@ -131,13 +126,11 @@ async def update_activity(self, context: TurnContext, activity: Activity):
:type activity: :class:`microsoft_agents.activity.Activity`
:return: Resource response for the updated activity.
:rtype: :class:`microsoft_agents.activity.ResourceResponse`
:raises TypeError: If context or activity are None/invalid.
:raises TypeError: activity.id is None
"""
if not context:
raise TypeError("Expected TurnContext but got None instead")

if activity is None:
raise TypeError("Expected Activity but got None instead")
if activity.id is None:
raise TypeError("Expected Activity with an id but got None instead")

with spans.AdapterUpdateActivity(activity):

Expand All @@ -146,7 +139,9 @@ async def update_activity(self, context: TurnContext, activity: Activity):
context.turn_state.get(self._AGENT_CONNECTOR_CLIENT_KEY),
)
if not connector_client:
raise Error("Unable to extract ConnectorClient from turn context.")
raise RuntimeError(
"Unable to extract ConnectorClient from turn context."
)

return await connector_client.conversations.update_activity(
activity.conversation.id, activity.id, activity
Expand All @@ -162,13 +157,12 @@ async def delete_activity(
:type context: :class:`microsoft_agents.hosting.core.turn_context.TurnContext`
:param reference: Reference to the conversation and activity to delete.
:type reference: :class:`microsoft_agents.activity.ConversationReference`
:raises TypeError: If context or reference are None/invalid.
:raises TypeError: reference.conversation or reference.activity_id is None
"""
if not context:
raise TypeError("Expected TurnContext but got None instead")

if not reference:
raise TypeError("Expected ConversationReference but got None instead")
if not reference.conversation or not reference.activity_id:
raise TypeError(
"Expected ConversationReference with conversation and activity_id but got None instead"
)
Comment thread
rodrigobr-msft marked this conversation as resolved.

with spans.AdapterDeleteActivity(context.activity):

Expand All @@ -177,7 +171,9 @@ async def delete_activity(
context.turn_state.get(self._AGENT_CONNECTOR_CLIENT_KEY),
)
if not connector_client:
raise Error("Unable to extract ConnectorClient from turn context.")
raise RuntimeError(
"Unable to extract ConnectorClient from turn context."
)

await connector_client.conversations.delete_activity(
reference.conversation.id, reference.activity_id
Expand Down Expand Up @@ -238,7 +234,7 @@ async def continue_conversation_with_claims(
:param callback: The method to call for the resulting agent turn.
:type callback: Callable[[:class:`microsoft_agents.hosting.core.turn_context.TurnContext`], Awaitable]
:param audience: The audience for the conversation.
:type audience: Optional[str]
:type audience: str | None
"""
with spans.AdapterContinueConversation(continuation_activity):
return await self.process_proactive(
Expand All @@ -261,12 +257,6 @@ async def create_conversation( # pylint: disable=arguments-differ
raise TypeError(
"CloudAdapter.create_conversation(): service_url is required."
)
if not conversation_parameters:
raise TypeError(
"CloudAdapter.create_conversation(): conversation_parameters is required."
)
if not callback:
raise TypeError("CloudAdapter.create_conversation(): callback is required.")

# Create a ClaimsIdentity, to create the connector and for adding to the turn context.
claims_identity = self.create_claims_identity(agent_app_id)
Expand Down Expand Up @@ -369,7 +359,7 @@ async def process_activity(
claims_identity: ClaimsIdentity,
activity: Activity,
callback: Callable[[TurnContext], Awaitable],
):
) -> InvokeResponse | None:
"""
Creates a turn context and runs the middleware pipeline for an incoming activity.

Expand All @@ -381,7 +371,7 @@ async def process_activity(
:type callback: Callable[[:class:`microsoft_agents.hosting.core.turn_context.TurnContext`], Awaitable]

:return: A task that represents the work queued to execute.
:rtype: Optional[:class:`microsoft_agents.activity.InvokeResponse`]
:rtype: :class:`microsoft_agents.activity.InvokeResponse` | None

.. note::
This class processes an activity received by the agents web server. This includes any messages
Expand Down Expand Up @@ -424,7 +414,7 @@ async def process_activity(
context.turn_state[self.USER_TOKEN_CLIENT_KEY] = user_token_client

# Create the connector client to use for outbound requests.
connector_client: Optional[ConnectorClient] = None
connector_client: ConnectorClient | None = None
if self._resolve_if_connector_client_is_needed(activity):
connector_client = (
await self._channel_service_client_factory.create_connector_client(
Expand Down Expand Up @@ -466,8 +456,6 @@ def create_claims_identity(self, agent_app_id: str = "") -> ClaimsIdentity:

@staticmethod
def _validate_continuation_activity(continuation_activity: Activity):
if not continuation_activity:
raise TypeError("CloudAdapter: continuation_activity is required.")

if not continuation_activity.conversation:
raise TypeError(
Expand Down Expand Up @@ -506,7 +494,7 @@ def _create_turn_context(
claims_identity: ClaimsIdentity,
oauth_scope: str,
callback: Callable[[TurnContext], Awaitable],
activity: Optional[Activity] = None,
activity: Activity | None = None,
) -> TurnContext:
context = TurnContext(self, activity, claims_identity)

Expand All @@ -519,13 +507,13 @@ def _create_turn_context(

return context

def _process_turn_results(self, context: TurnContext) -> Optional[InvokeResponse]:
def _process_turn_results(self, context: TurnContext) -> InvokeResponse | None:
"""Process the results of a turn and return the appropriate response.

:param context: The turn context
:type context: :class:`microsoft_agents.hosting.core.turn_context.TurnContext`
:return: The invoke response, if applicable
:rtype: Optional[:class:`microsoft_agents.activity.InvokeResponse`]
:rtype: :class:`microsoft_agents.activity.InvokeResponse` | None
"""
# Handle ExpectedReplies scenarios where all activities have been
# buffered and sent back at once in an invoke response.
Expand All @@ -542,11 +530,11 @@ def _process_turn_results(self, context: TurnContext) -> Optional[InvokeResponse
if context.activity.type == ActivityTypes.invoke:

with spans.AdapterSendActivities([context.activity]):
activity_invoke_response: Activity = context.turn_state.get(
self.INVOKE_RESPONSE_KEY
activity_invoke_response: Activity | None = cast(
Activity | None, context.turn_state.get(self.INVOKE_RESPONSE_KEY)
)
if not activity_invoke_response:
return InvokeResponse(status=HTTPStatus.OK)
return InvokeResponse(status=HTTPStatus.NOT_IMPLEMENTED)

return InvokeResponse.model_validate(activity_invoke_response.value)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@


class ChannelHostProtocol(Protocol):

host_endpoint: str
host_app_id: str
channels: dict[str, ChannelInfoProtocol]

def __init__(
self,
host_endpoint: str,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ async def post_activity(
conversation_id: str,
activity: Activity,
*,
response_body_type: type[AgentsModel] = None,
response_body_type: type[AgentsModel] | None = None,
**kwargs,
) -> InvokeResponse:
raise NotImplementedError()
Loading
Loading