Skip to content

Commit 70c86a3

Browse files
authored
Merge branch 'main' into mehak/streaming
2 parents ab3367d + 22b9453 commit 70c86a3

29 files changed

+469
-536
lines changed

packages/a2aprotocol/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "microsoft-teams-a2a"
3-
version = "2.0.0a3"
3+
version = "2.0.0a4"
44
description = "plugin that enables your teams agent to be used as an a2a agent"
55
authors = [{ name = "Microsoft", email = "[email protected]" }]
66
readme = "README.md"

packages/ai/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "microsoft-teams-ai"
3-
version = "2.0.0a3"
3+
version = "2.0.0a4"
44
description = "package to handle interacting with ai or llms"
55
authors = [{ name = "Microsoft", email = "[email protected]" }]
66
readme = "README.md"

packages/api/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "microsoft-teams-api"
3-
version = "2.0.0a3"
3+
version = "2.0.0a4"
44
description = "API package for Microsoft Teams"
55
readme = "README.md"
66
repository = "https://github.com/microsoft/teams.py"

packages/api/src/microsoft/teams/api/models/messaging_extension/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from .messaging_extension_action import MessagingExtensionAction
77
from .messaging_extension_action_response import MessagingExtensionActionResponse
88
from .messaging_extension_attachment import MessagingExtensionAttachment
9+
from .messaging_extension_attachment_layout import MessagingExtensionAttachmentLayout
910
from .messaging_extension_parameter import MessagingExtensionParameter
1011
from .messaging_extension_query import MessagingExtensionQuery, MessagingExtensionQueryOptions
1112
from .messaging_extension_response import MessagingExtensionResponse
@@ -17,6 +18,7 @@
1718
"MessagingExtensionAction",
1819
"MessagingExtensionActionResponse",
1920
"MessagingExtensionAttachment",
21+
"MessagingExtensionAttachmentLayout",
2022
"MessagingExtensionParameter",
2123
"MessagingExtensionQuery",
2224
"MessagingExtensionQueryOptions",
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""
2+
Copyright (c) Microsoft Corporation. All rights reserved.
3+
Licensed under the MIT License.
4+
"""
5+
6+
from enum import Enum
7+
8+
9+
class MessagingExtensionAttachmentLayout(str, Enum):
10+
"""Enum for messaging extension attachment layout types."""
11+
12+
LIST = "list"
13+
GRID = "grid"

packages/api/src/microsoft/teams/api/models/messaging_extension/messaging_extension_result.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@
88
from typing import List, Optional
99

1010
from ...models.activity import Activity as ActivityBase
11-
from ..attachment.attachment_layout import AttachmentLayout
1211
from ..custom_base_model import CustomBaseModel
1312
from .messaging_extension_attachment import MessagingExtensionAttachment
13+
from .messaging_extension_attachment_layout import MessagingExtensionAttachmentLayout
1414
from .messaging_extension_result_type import MessagingExtensionResultType
1515
from .messaging_extension_suggested_action import MessagingExtensionSuggestedAction
1616

@@ -20,7 +20,7 @@ class MessagingExtensionResult(CustomBaseModel):
2020
Messaging extension result
2121
"""
2222

23-
attachment_layout: Optional[AttachmentLayout] = None
23+
attachment_layout: Optional[MessagingExtensionAttachmentLayout] = None
2424
"Hint for how to deal with multiple attachments."
2525

2626
type: Optional[MessagingExtensionResultType] = None

packages/apps/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "microsoft-teams-apps"
3-
version = "2.0.0a3"
3+
version = "2.0.0a4"
44
description = "The app package for a Microsoft Teams agent"
55
authors = [{ name = "Microsoft", email = "[email protected]" }]
66
readme = "README.md"

packages/apps/src/microsoft/teams/apps/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
from . import auth, contexts, events, plugins
77
from .app import App
8-
from .app_tokens import AppTokens
98
from .auth import * # noqa: F403
109
from .contexts import * # noqa: F403
1110
from .events import * # noqa: F401, F403
@@ -16,7 +15,7 @@
1615
from .routing import ActivityContext
1716

1817
# Combine all exports from submodules
19-
__all__: list[str] = ["App", "AppOptions", "HttpPlugin", "HttpStream", "ActivityContext", "AppTokens"]
18+
__all__: list[str] = ["App", "AppOptions", "HttpPlugin", "HttpStream", "ActivityContext"]
2019
__all__.extend(auth.__all__)
2120
__all__.extend(events.__all__)
2221
__all__.extend(plugins.__all__)

packages/apps/src/microsoft/teams/apps/app.py

Lines changed: 22 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
ConversationAccount,
2222
ConversationReference,
2323
Credentials,
24-
JsonWebToken,
2524
MessageActivityInput,
2625
TokenCredentials,
2726
)
@@ -32,7 +31,6 @@
3231
from .app_oauth import OauthHandlers
3332
from .app_plugins import PluginProcessor
3433
from .app_process import ActivityProcessor
35-
from .app_tokens import AppTokens
3634
from .auth import TokenValidator
3735
from .auth.remote_function_jwt_middleware import remote_function_jwt_validation
3836
from .container import Container
@@ -45,20 +43,20 @@
4543
get_event_type_from_signature,
4644
is_registered_event,
4745
)
48-
from .graph_token_manager import GraphTokenManager
4946
from .http_plugin import HttpPlugin
5047
from .options import AppOptions, InternalAppOptions
5148
from .plugins import PluginBase, PluginStartEvent, get_metadata
5249
from .routing import ActivityHandlerMixin, ActivityRouter
5350
from .routing.activity_context import ActivityContext
51+
from .token_manager import TokenManager
5452

5553
version = importlib.metadata.version("microsoft-teams-apps")
5654

5755
F = TypeVar("F", bound=Callable[..., Any])
5856
FCtx = TypeVar("FCtx", bound=Callable[[FunctionContext[Any]], Any])
5957
load_dotenv(find_dotenv(usecwd=True))
6058

61-
USER_AGENT = f"teams.py[app]/{version}"
59+
USER_AGENT = f"teams.py[apps]/{version}"
6260

6361

6462
class App(ActivityHandlerMixin):
@@ -83,22 +81,26 @@ def __init__(self, **options: Unpack[AppOptions]):
8381
self._events = EventEmitter[EventType]()
8482
self._router = ActivityRouter()
8583

86-
self._tokens = AppTokens()
8784
self.credentials = self._init_credentials()
8885

86+
self._token_manager = TokenManager(
87+
http_client=self.http_client,
88+
credentials=self.credentials,
89+
logger=self.log,
90+
default_connection_name=self.options.default_connection_name,
91+
)
92+
8993
self.container = Container()
9094
self.container.set_provider("id", providers.Object(self.id))
91-
self.container.set_provider("name", providers.Object(self.name))
9295
self.container.set_provider("credentials", providers.Object(self.credentials))
93-
self.container.set_provider("bot_token", providers.Callable(lambda: self.tokens.bot))
94-
self.container.set_provider("graph_token", providers.Callable(lambda: self.tokens.graph))
96+
self.container.set_provider("bot_token", providers.Factory(lambda: self._get_or_get_bot_token))
9597
self.container.set_provider("logger", providers.Object(self.log))
9698
self.container.set_provider("storage", providers.Object(self.storage))
9799
self.container.set_provider(self.http_client.__class__.__name__, providers.Factory(lambda: self.http_client))
98100

99101
self.api = ApiClient(
100102
"https://smba.trafficmanager.net/teams",
101-
self.http_client.clone(ClientOptions(token=lambda: self.tokens.bot)),
103+
self.http_client.clone(ClientOptions(token=self._get_or_get_bot_token)),
102104
)
103105

104106
plugins: List[PluginBase] = list(self.options.plugins)
@@ -125,20 +127,14 @@ def __init__(self, **options: Unpack[AppOptions]):
125127
self._running = False
126128

127129
# initialize all event, activity, and plugin processors
128-
self.graph_token_manager = GraphTokenManager(
129-
api_client=self.api,
130-
credentials=self.credentials,
131-
logger=self.log,
132-
)
133130
self.activity_processor = ActivityProcessor(
134131
self._router,
135132
self.log,
136133
self.id,
137134
self.storage,
138135
self.options.default_connection_name,
139136
self.http_client,
140-
self.tokens,
141-
self.graph_token_manager,
137+
self._token_manager,
142138
)
143139
self.event_manager = EventManager(self._events, self.activity_processor)
144140
self.activity_processor.event_manager = self.event_manager
@@ -169,11 +165,6 @@ def is_running(self) -> bool:
169165
"""Whether the app is currently running."""
170166
return self._running
171167

172-
@property
173-
def tokens(self) -> AppTokens:
174-
"""Current authentication tokens."""
175-
return self._tokens
176-
177168
@property
178169
def logger(self) -> Logger:
179170
"""The logger instance used by the app."""
@@ -191,17 +182,10 @@ def router(self) -> ActivityRouter:
191182

192183
@property
193184
def id(self) -> Optional[str]:
194-
"""The app's ID from tokens."""
195-
return (
196-
self._tokens.bot.app_id if self._tokens.bot else self._tokens.graph.app_id if self._tokens.graph else None
197-
)
198-
199-
@property
200-
def name(self) -> Optional[str]:
201-
"""The app's name from tokens."""
202-
return getattr(self._tokens.bot, "app_display_name", None) or getattr(
203-
self._tokens.graph, "app_display_name", None
204-
)
185+
"""The app's ID from credentials."""
186+
if not self.credentials:
187+
return None
188+
return self.credentials.client_id
205189

206190
async def start(self, port: Optional[int] = None) -> None:
207191
"""
@@ -220,9 +204,6 @@ async def start(self, port: Optional[int] = None) -> None:
220204
self._port = port or int(os.getenv("PORT", "3978"))
221205

222206
try:
223-
await self._refresh_tokens(force=True)
224-
self._running = True
225-
226207
for plugin in self.plugins:
227208
# Inject the dependencies
228209
self._plugin_processor.inject(plugin)
@@ -234,6 +215,7 @@ async def on_http_ready() -> None:
234215
self.log.info("Teams app started successfully")
235216
assert self._port is not None, "Port must be set before emitting start event"
236217
self._events.emit("start", StartEvent(port=self._port))
218+
self._running = True
237219

238220
self.http.on_ready_callback = on_http_ready
239221

@@ -280,13 +262,13 @@ async def on_http_stopped() -> None:
280262
async def send(self, conversation_id: str, activity: str | ActivityParams | AdaptiveCard):
281263
"""Send an activity proactively."""
282264

283-
if self.id is None or self.name is None:
265+
if self.id is None:
284266
raise ValueError("app not started")
285267

286268
conversation_ref = ConversationReference(
287269
channel_id="msteams",
288270
service_url=self.api.service_url,
289-
bot=Account(id=self.id, name=self.name, role="bot"),
271+
bot=Account(id=self.id, role="bot"),
290272
conversation=ConversationAccount(id=conversation_id, conversation_type="personal"),
291273
)
292274

@@ -326,65 +308,6 @@ def _init_credentials(self) -> Optional[Credentials]:
326308

327309
return None
328310

329-
async def _refresh_tokens(self, force: bool = False) -> None:
330-
"""Refresh bot and graph tokens."""
331-
await asyncio.gather(self._refresh_bot_token(force), self._refresh_graph_token(force), return_exceptions=True)
332-
333-
async def _refresh_bot_token(self, force: bool = False) -> None:
334-
"""Refresh the bot authentication token."""
335-
if not self.credentials:
336-
self.log.warning("No credentials provided, skipping bot token refresh")
337-
return
338-
339-
if not force and self._tokens.bot and not self._tokens.bot.is_expired():
340-
return
341-
342-
if self._tokens.bot:
343-
self.log.debug("Refreshing bot token")
344-
345-
try:
346-
token_response = await self.api.bots.token.get(self.credentials)
347-
self._tokens.bot = JsonWebToken(token_response.access_token)
348-
self.log.debug("Bot token refreshed successfully")
349-
350-
except Exception as error:
351-
self.log.error(f"Failed to refresh bot token: {error}")
352-
353-
self._events.emit("error", ErrorEvent(error, context={"method": "_refresh_bot_token"}))
354-
raise
355-
356-
async def _refresh_graph_token(self, force: bool = False) -> None:
357-
"""Refresh the Graph API token."""
358-
if not self.credentials:
359-
self.log.warning("No credentials provided, skipping graph token refresh")
360-
return
361-
362-
if not force and self._tokens.graph and not self._tokens.graph.is_expired():
363-
return
364-
365-
if self._tokens.graph:
366-
self.log.debug("Refreshing graph token")
367-
368-
try:
369-
# Use GraphTokenManager for tenant-aware token management
370-
tenant_id = self.credentials.tenant_id if self.credentials else None
371-
token = await self.graph_token_manager.get_token(tenant_id)
372-
373-
if token:
374-
self._tokens.graph = token
375-
self.log.debug("Graph token refreshed successfully")
376-
377-
# Emit token acquired event
378-
self._events.emit("token", {"type": "graph", "token": self._tokens.graph})
379-
else:
380-
self.log.debug("Failed to get graph token from GraphTokenManager")
381-
382-
except Exception as error:
383-
self.log.error(f"Failed to refresh graph token: {error}")
384-
385-
self._events.emit("error", ErrorEvent(error, context={"method": "_refresh_graph_token"}))
386-
raise
387-
388311
@overload
389312
def event(self, func_or_event_type: F) -> F:
390313
"""Register event handler with auto-detected type from function signature."""
@@ -521,7 +444,6 @@ async def endpoint(req: Request):
521444
async def call_next(r: Request) -> Any:
522445
ctx = FunctionContext(
523446
id=self.id,
524-
name=self.name,
525447
api=self.api,
526448
http=self.http,
527449
log=self.log,
@@ -541,3 +463,6 @@ async def call_next(r: Request) -> Any:
541463

542464
# Named decoration: @app.func("name")
543465
return decorator
466+
467+
async def _get_or_get_bot_token(self):
468+
return await self._token_manager.get_bot_token()

0 commit comments

Comments
 (0)