mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-02 12:05:54 +00:00
Compare commits
1 Commits
v4.8.6
...
feat/teams
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3740eed9e |
162
TEAMS_ADAPTER_IMPLEMENTATION.md
Normal file
162
TEAMS_ADAPTER_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# Microsoft Teams Adapter Implementation
|
||||
|
||||
## Overview
|
||||
|
||||
A new Microsoft Teams platform adapter has been added to LangBot, enabling support for both personal chats and channel/group chats on Microsoft Teams.
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
### New Files
|
||||
|
||||
1. **`src/langbot/pkg/platform/sources/teams.py`** - Main adapter implementation
|
||||
- `TeamsMessageConverter`: Converts between LangBot message format and Teams Activity format
|
||||
- `TeamsEventConverter`: Converts between Teams Activity events and LangBot platform events
|
||||
- `TeamsAdapter`: Main adapter class with webhook handling
|
||||
|
||||
2. **`src/langbot/pkg/platform/sources/teams.yaml`** - Adapter manifest
|
||||
- Defines adapter metadata, configuration schema, and execution details
|
||||
- Configuration fields: `app_id` and `app_password`
|
||||
|
||||
### Modified Files
|
||||
|
||||
1. **`pyproject.toml`** - Added dependencies:
|
||||
- `botbuilder-core>=4.15.0`
|
||||
- `botbuilder-schema>=4.15.0`
|
||||
- `botframework-connector>=4.15.0`
|
||||
- Added "teams" keyword
|
||||
|
||||
## Features
|
||||
|
||||
### Supported Message Types
|
||||
|
||||
- ✅ Plain text messages
|
||||
- ✅ Image attachments (base64, URL, and file path)
|
||||
- ✅ @mentions (converted to At components)
|
||||
- ✅ Message replies with quote support
|
||||
|
||||
### Supported Chat Types
|
||||
|
||||
- ✅ Personal chats (1-on-1 conversations)
|
||||
- ✅ Channel chats (group/team conversations)
|
||||
|
||||
### Message Handling
|
||||
|
||||
- **Incoming Messages**: Received via webhook at the unified webhook endpoint
|
||||
- **Outgoing Messages**: Sent via Bot Framework SDK using conversation references
|
||||
- **Event Types**: FriendMessage (personal) and GroupMessage (channel/group)
|
||||
|
||||
## Configuration Requirements
|
||||
|
||||
### Azure Setup
|
||||
|
||||
1. **Register an Azure AD Application**:
|
||||
- Go to Azure Portal → Azure Active Directory → App registrations
|
||||
- Create a new registration
|
||||
- Note the **Application (client) ID** - this is your `app_id`
|
||||
- Create a **Client Secret** - this is your `app_password`
|
||||
|
||||
2. **Create Azure Bot Resource**:
|
||||
- Go to Azure Portal → Create a resource → Azure Bot
|
||||
- Link it to your Azure AD application
|
||||
- Enable the Microsoft Teams channel
|
||||
- Set the messaging endpoint to: `https://<your-domain>/bots/<bot-uuid>`
|
||||
|
||||
### LangBot Configuration
|
||||
|
||||
When creating a Teams bot in LangBot, provide:
|
||||
|
||||
```yaml
|
||||
adapter: teams
|
||||
adapter_config:
|
||||
app_id: "<your-microsoft-app-id>"
|
||||
app_password: "<your-microsoft-app-password>"
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
### Webhook Mode
|
||||
|
||||
The Teams adapter operates in webhook mode, similar to the Slack adapter:
|
||||
- Integrates with LangBot's unified webhook system
|
||||
- Receives messages at `/bots/<bot-uuid>` endpoint
|
||||
- No independent server process required
|
||||
|
||||
### Message Flow
|
||||
|
||||
1. **Incoming**:
|
||||
- Teams → Azure Bot Service → LangBot Webhook
|
||||
- Bot Framework validates the request
|
||||
- Activity converted to LangBot event format
|
||||
- Event dispatched to registered listeners
|
||||
|
||||
2. **Outgoing**:
|
||||
- LangBot message chain → Teams Activity format
|
||||
- Reply sent via Bot Framework adapter
|
||||
- Uses conversation reference for proper routing
|
||||
|
||||
## Authentication
|
||||
|
||||
- Uses Bot Framework authentication with Microsoft App credentials
|
||||
- JWT token validation handled by `BotFrameworkAdapter`
|
||||
- Authorization header validated on each incoming request
|
||||
|
||||
## Limitations & Notes
|
||||
|
||||
1. **Direct Send**: The `send_message` method is limited - use `reply_message` for best results
|
||||
2. **Conversation References**: The adapter uses conversation references from incoming activities to send replies
|
||||
3. **Image Handling**: Images are converted to inline attachments using data URIs
|
||||
4. **Streaming**: Streaming replies are not yet implemented
|
||||
|
||||
## Testing
|
||||
|
||||
To test the adapter:
|
||||
|
||||
1. Install dependencies: `uv sync`
|
||||
2. Verify adapter initialization: `uv run python -c "from src.langbot.pkg.platform.sources.teams import TeamsAdapter; print('OK')"`
|
||||
3. Configure a Teams bot in LangBot with your Azure credentials
|
||||
4. Expose the LangBot API endpoint publicly (use ngrok or similar)
|
||||
5. Set the messaging endpoint in Azure Bot Service
|
||||
6. Add the bot to Teams and start chatting
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Pydantic Validation Error on Initialization
|
||||
|
||||
**Fixed**: The adapter was updated to properly handle optional fields (`adapter`, `app`, `bot_uuid`) by:
|
||||
- Setting `default=None` in pydantic.Field definitions
|
||||
- Not passing these fields to `super().__init__()`
|
||||
- Setting the adapter instance after parent class initialization
|
||||
|
||||
This resolves the validation error: "Input should be an instance of Quart" / "Input should be a valid string"
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Potential improvements:
|
||||
- Adaptive Cards support
|
||||
- Rich message formatting (markdown, cards)
|
||||
- File attachments (non-image)
|
||||
- Streaming message support
|
||||
- Proactive messaging
|
||||
- Team/channel member list retrieval
|
||||
- Reaction handling
|
||||
|
||||
## Dependencies
|
||||
|
||||
The following Python packages were added:
|
||||
- `botbuilder-core` - Core Bot Framework functionality
|
||||
- `botbuilder-schema` - Activity and entity schemas
|
||||
- `botframework-connector` - Bot Framework connector for authentication
|
||||
|
||||
## References
|
||||
|
||||
- [Microsoft Teams Bot Framework Documentation](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/bot-features)
|
||||
- [Bot Framework SDK for Python](https://github.com/microsoft/botbuilder-python)
|
||||
- [Teams Conversation Bot Sample](https://learn.microsoft.com/en-us/samples/officedev/microsoft-teams-samples/officedev-microsoft-teams-samples-bot-conversation-python/)
|
||||
|
||||
## Sources
|
||||
|
||||
Research sources consulted during implementation:
|
||||
- [Create an Incoming Webhook - Teams | Microsoft Learn](https://learn.microsoft.com/en-us/microsoftteams/platform/webhooks-and-connectors/how-to/add-incoming-webhook)
|
||||
- [Teams Conversation Bot - Code Samples | Microsoft Learn](https://learn.microsoft.com/en-us/samples/officedev/microsoft-teams-samples/officedev-microsoft-teams-samples-bot-conversation-python/)
|
||||
- [GitHub - microsoft/botbuilder-python](https://github.com/microsoft/botbuilder-python)
|
||||
- [Tools and Bot Framework SDKs for Bots - Teams](https://learn.microsoft.com/en-us/microsoftteams/platform/bots/bot-features)
|
||||
@@ -14,6 +14,9 @@ dependencies = [
|
||||
"anthropic>=0.51.0",
|
||||
"argon2-cffi>=23.1.0",
|
||||
"async-lru>=2.0.5",
|
||||
"botbuilder-core>=4.15.0",
|
||||
"botbuilder-schema>=4.15.0",
|
||||
"botframework-connector>=4.15.0",
|
||||
"certifi>=2025.4.26",
|
||||
"colorlog~=6.6.0",
|
||||
"cryptography>=44.0.3",
|
||||
@@ -73,6 +76,7 @@ keywords = [
|
||||
"bot",
|
||||
"agent",
|
||||
"telegram",
|
||||
"teams",
|
||||
"plugins",
|
||||
"openai",
|
||||
"instant-messaging",
|
||||
|
||||
398
src/langbot/pkg/platform/sources/teams.py
Normal file
398
src/langbot/pkg/platform/sources/teams.py
Normal file
@@ -0,0 +1,398 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import typing
|
||||
import asyncio
|
||||
import traceback
|
||||
import base64
|
||||
import datetime
|
||||
import aiohttp
|
||||
import pydantic
|
||||
|
||||
from botbuilder.core import BotFrameworkAdapter, BotFrameworkAdapterSettings, TurnContext
|
||||
from botbuilder.schema import Activity, ActivityTypes, ChannelAccount, ConversationAccount
|
||||
from botframework.connector.auth import MicrosoftAppCredentials
|
||||
from quart import Quart, request, Response
|
||||
|
||||
import langbot_plugin.api.definition.abstract.platform.adapter as abstract_platform_adapter
|
||||
import langbot_plugin.api.entities.builtin.platform.events as platform_events
|
||||
import langbot_plugin.api.entities.builtin.platform.message as platform_message
|
||||
import langbot_plugin.api.entities.builtin.platform.entities as platform_entities
|
||||
import langbot_plugin.api.definition.abstract.platform.event_logger as abstract_platform_logger
|
||||
|
||||
|
||||
class TeamsMessageConverter(abstract_platform_adapter.AbstractMessageConverter):
|
||||
"""Convert messages between LangBot format and Teams Activity format"""
|
||||
|
||||
@staticmethod
|
||||
async def yiri2target(message_chain: platform_message.MessageChain) -> dict:
|
||||
"""Convert LangBot message chain to Teams message format"""
|
||||
text_content = []
|
||||
attachments = []
|
||||
|
||||
for component in message_chain:
|
||||
if isinstance(component, platform_message.Plain):
|
||||
text_content.append(component.text)
|
||||
elif isinstance(component, platform_message.Image):
|
||||
# Teams supports image attachments
|
||||
image_data = None
|
||||
image_type = 'image/png'
|
||||
|
||||
if component.base64:
|
||||
# Extract base64 data
|
||||
if component.base64.startswith('data:'):
|
||||
parts = component.base64.split(',')
|
||||
header = parts[0]
|
||||
if 'image/' in header:
|
||||
image_type = header.split(';')[0].replace('data:', '')
|
||||
image_data = parts[1] if len(parts) > 1 else component.base64
|
||||
else:
|
||||
image_data = component.base64
|
||||
elif component.url:
|
||||
# For URLs, Teams can display them inline
|
||||
text_content.append(f'\n{component.url}')
|
||||
continue
|
||||
elif component.path:
|
||||
try:
|
||||
with open(component.path, 'rb') as f:
|
||||
image_bytes = f.read()
|
||||
image_data = base64.b64encode(image_bytes).decode('utf-8')
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
if image_data:
|
||||
# Create inline image attachment
|
||||
attachments.append({
|
||||
'contentType': image_type,
|
||||
'contentUrl': f'data:{image_type};base64,{image_data}',
|
||||
'name': 'image'
|
||||
})
|
||||
|
||||
result = {
|
||||
'text': ''.join(text_content),
|
||||
}
|
||||
|
||||
if attachments:
|
||||
result['attachments'] = attachments
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
async def target2yiri(activity: Activity) -> platform_message.MessageChain:
|
||||
"""Convert Teams Activity to LangBot message chain"""
|
||||
components = []
|
||||
|
||||
# Add message source
|
||||
components.append(
|
||||
platform_message.Source(
|
||||
id=activity.id,
|
||||
time=activity.timestamp if activity.timestamp else datetime.datetime.now()
|
||||
)
|
||||
)
|
||||
|
||||
# Handle mentions (convert to At components)
|
||||
if activity.entities:
|
||||
for entity in activity.entities:
|
||||
if entity.type == 'mention':
|
||||
mentioned = entity.mentioned
|
||||
if mentioned:
|
||||
components.append(platform_message.At(target=str(mentioned.id)))
|
||||
|
||||
# Add text content
|
||||
if activity.text:
|
||||
text = activity.text
|
||||
# Remove bot mentions from text
|
||||
if activity.entities:
|
||||
for entity in activity.entities:
|
||||
if entity.type == 'mention' and entity.text:
|
||||
text = text.replace(entity.text, '').strip()
|
||||
|
||||
if text:
|
||||
components.append(platform_message.Plain(text=text))
|
||||
|
||||
# Handle attachments (images, files, etc.)
|
||||
if activity.attachments:
|
||||
for attachment in activity.attachments:
|
||||
if attachment.content_type and 'image' in attachment.content_type:
|
||||
# Download and convert image to base64
|
||||
if attachment.content_url:
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(attachment.content_url) as response:
|
||||
image_data = await response.read()
|
||||
image_base64 = base64.b64encode(image_data).decode('utf-8')
|
||||
content_type = attachment.content_type or 'image/png'
|
||||
components.append(
|
||||
platform_message.Image(
|
||||
base64=f'data:{content_type};base64,{image_base64}'
|
||||
)
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return platform_message.MessageChain(components)
|
||||
|
||||
|
||||
class TeamsEventConverter(abstract_platform_adapter.AbstractEventConverter):
|
||||
"""Convert events between Teams Activity and LangBot platform events"""
|
||||
|
||||
@staticmethod
|
||||
async def yiri2target(event: platform_events.Event) -> Activity:
|
||||
"""Convert LangBot event to Teams Activity"""
|
||||
return event.source_platform_object
|
||||
|
||||
@staticmethod
|
||||
async def target2yiri(activity: Activity, bot_id: str) -> platform_events.Event:
|
||||
"""Convert Teams Activity to LangBot event"""
|
||||
message_chain = await TeamsMessageConverter.target2yiri(activity)
|
||||
|
||||
# Determine if it's a personal or channel/group chat
|
||||
conversation_type = activity.conversation.conversation_type if activity.conversation else None
|
||||
|
||||
if conversation_type == 'personal':
|
||||
# Personal chat (1-on-1)
|
||||
return platform_events.FriendMessage(
|
||||
sender=platform_entities.Friend(
|
||||
id=activity.from_property.id,
|
||||
nickname=activity.from_property.name or activity.from_property.id,
|
||||
remark=activity.from_property.id,
|
||||
),
|
||||
message_chain=message_chain,
|
||||
time=activity.timestamp.timestamp() if activity.timestamp else datetime.datetime.now().timestamp(),
|
||||
source_platform_object=activity,
|
||||
)
|
||||
else:
|
||||
# Channel or group chat
|
||||
return platform_events.GroupMessage(
|
||||
sender=platform_entities.GroupMember(
|
||||
id=activity.from_property.id,
|
||||
member_name=activity.from_property.name or activity.from_property.id,
|
||||
permission=platform_entities.Permission.Member,
|
||||
group=platform_entities.Group(
|
||||
id=activity.conversation.id,
|
||||
name=activity.conversation.name or activity.conversation.id,
|
||||
permission=platform_entities.Permission.Member,
|
||||
),
|
||||
special_title='',
|
||||
join_timestamp=0,
|
||||
last_speak_timestamp=0,
|
||||
mute_time_remaining=0,
|
||||
),
|
||||
message_chain=message_chain,
|
||||
time=activity.timestamp.timestamp() if activity.timestamp else datetime.datetime.now().timestamp(),
|
||||
source_platform_object=activity,
|
||||
)
|
||||
|
||||
|
||||
class TeamsAdapter(abstract_platform_adapter.AbstractMessagePlatformAdapter):
|
||||
"""Microsoft Teams platform adapter for LangBot"""
|
||||
|
||||
adapter: BotFrameworkAdapter = pydantic.Field(exclude=True, default=None)
|
||||
app: Quart = pydantic.Field(exclude=True, default=None)
|
||||
bot_uuid: typing.Optional[str] = None
|
||||
|
||||
message_converter: TeamsMessageConverter = TeamsMessageConverter()
|
||||
event_converter: TeamsEventConverter = TeamsEventConverter()
|
||||
|
||||
config: dict
|
||||
|
||||
listeners: typing.Dict[
|
||||
typing.Type[platform_events.Event],
|
||||
typing.Callable[[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None],
|
||||
] = {}
|
||||
|
||||
def __init__(self, config: dict, logger: abstract_platform_logger.AbstractEventLogger, **kwargs):
|
||||
"""Initialize Teams adapter with app credentials"""
|
||||
# Validate required config
|
||||
if 'app_id' not in config or 'app_password' not in config:
|
||||
raise ValueError('Teams adapter requires app_id and app_password in configuration')
|
||||
|
||||
# Create Bot Framework adapter settings
|
||||
settings = BotFrameworkAdapterSettings(
|
||||
app_id=config['app_id'],
|
||||
app_password=config['app_password']
|
||||
)
|
||||
|
||||
# Create Bot Framework adapter
|
||||
adapter_instance = BotFrameworkAdapter(settings)
|
||||
|
||||
super().__init__(
|
||||
config=config,
|
||||
logger=logger,
|
||||
bot_account_id=config['app_id'],
|
||||
listeners={},
|
||||
**kwargs
|
||||
)
|
||||
|
||||
# Set the adapter after initialization
|
||||
self.adapter = adapter_instance
|
||||
|
||||
async def _handle_activity(self, activity: Activity):
|
||||
"""Internal method to handle incoming activities"""
|
||||
try:
|
||||
# Only process message activities from users (not bots)
|
||||
if activity.type == ActivityTypes.message:
|
||||
if activity.from_property and not (hasattr(activity.from_property, 'role') and activity.from_property.role == 'bot'):
|
||||
# Convert to LangBot event
|
||||
lb_event = await self.event_converter.target2yiri(activity, self.bot_account_id)
|
||||
|
||||
# Call appropriate listener
|
||||
event_type = type(lb_event)
|
||||
if event_type in self.listeners:
|
||||
await self.listeners[event_type](lb_event, self)
|
||||
except Exception as e:
|
||||
await self.logger.error(f'Error handling Teams activity: {str(e)}\n{traceback.format_exc()}')
|
||||
|
||||
async def send_message(self, target_type: str, target_id: str, message: platform_message.MessageChain):
|
||||
"""Send a message to a specific target"""
|
||||
try:
|
||||
message_data = await self.message_converter.yiri2target(message)
|
||||
|
||||
# Create activity
|
||||
activity = Activity(
|
||||
type=ActivityTypes.message,
|
||||
text=message_data.get('text', ''),
|
||||
attachments=message_data.get('attachments', []),
|
||||
conversation=ConversationAccount(id=target_id),
|
||||
)
|
||||
|
||||
# Send via Bot Framework adapter
|
||||
# Note: This requires a conversation reference which we would need to store
|
||||
# For now, this is a placeholder - full implementation would require conversation tracking
|
||||
await self.logger.warning('Direct send_message not fully implemented - use reply_message instead')
|
||||
except Exception as e:
|
||||
await self.logger.error(f'Error sending Teams message: {str(e)}')
|
||||
|
||||
async def reply_message(
|
||||
self,
|
||||
message_source: platform_events.MessageEvent,
|
||||
message: platform_message.MessageChain,
|
||||
quote_origin: bool = False,
|
||||
):
|
||||
"""Reply to a message"""
|
||||
try:
|
||||
assert isinstance(message_source.source_platform_object, Activity)
|
||||
source_activity: Activity = message_source.source_platform_object
|
||||
|
||||
message_data = await self.message_converter.yiri2target(message)
|
||||
|
||||
# Build conversation reference from source activity
|
||||
conversation_reference = TurnContext.get_conversation_reference(source_activity)
|
||||
|
||||
# Create reply activity
|
||||
async def send_reply(turn_context: TurnContext):
|
||||
reply_text = message_data.get('text', '')
|
||||
reply_attachments = message_data.get('attachments', [])
|
||||
|
||||
if quote_origin:
|
||||
# Include reply_to_id to quote the original message
|
||||
await turn_context.send_activity(
|
||||
Activity(
|
||||
type=ActivityTypes.message,
|
||||
text=reply_text,
|
||||
attachments=reply_attachments,
|
||||
reply_to_id=source_activity.id
|
||||
)
|
||||
)
|
||||
else:
|
||||
await turn_context.send_activity(
|
||||
Activity(
|
||||
type=ActivityTypes.message,
|
||||
text=reply_text,
|
||||
attachments=reply_attachments
|
||||
)
|
||||
)
|
||||
|
||||
# Continue conversation using the conversation reference
|
||||
await self.adapter.continue_conversation(
|
||||
conversation_reference,
|
||||
send_reply,
|
||||
self.bot_account_id
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
await self.logger.error(f'Error replying to Teams message: {str(e)}\n{traceback.format_exc()}')
|
||||
|
||||
def register_listener(
|
||||
self,
|
||||
event_type: typing.Type[platform_events.Event],
|
||||
callback: typing.Callable[
|
||||
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
|
||||
],
|
||||
):
|
||||
"""Register event listener"""
|
||||
self.listeners[event_type] = callback
|
||||
|
||||
def unregister_listener(
|
||||
self,
|
||||
event_type: typing.Type[platform_events.Event],
|
||||
_callback: typing.Callable[
|
||||
[platform_events.Event, abstract_platform_adapter.AbstractMessagePlatformAdapter], None
|
||||
],
|
||||
):
|
||||
"""Unregister event listener"""
|
||||
if event_type in self.listeners:
|
||||
del self.listeners[event_type]
|
||||
|
||||
def set_bot_uuid(self, bot_uuid: str):
|
||||
"""Set bot UUID for webhook URL generation"""
|
||||
self.bot_uuid = bot_uuid
|
||||
|
||||
async def handle_unified_webhook(self, _bot_uuid: str, _path: str, request_obj):
|
||||
"""Handle unified webhook requests from Teams
|
||||
|
||||
Args:
|
||||
_bot_uuid: Bot UUID (unused)
|
||||
_path: Sub-path (unused)
|
||||
request_obj: Quart Request object
|
||||
|
||||
Returns:
|
||||
Response data
|
||||
"""
|
||||
try:
|
||||
# Get request body
|
||||
body = await request_obj.get_json()
|
||||
|
||||
# Get authorization header
|
||||
auth_header = request_obj.headers.get('Authorization', '')
|
||||
|
||||
# Process activity
|
||||
async def bot_logic(turn_context: TurnContext):
|
||||
# Handle the activity
|
||||
await self._handle_activity(turn_context.activity)
|
||||
|
||||
# Process the request through Bot Framework adapter
|
||||
await self.adapter.process_activity(body, auth_header, bot_logic)
|
||||
|
||||
return Response(status=200)
|
||||
|
||||
except Exception as e:
|
||||
await self.logger.error(f'Error processing Teams webhook: {str(e)}\n{traceback.format_exc()}')
|
||||
return Response(status=500)
|
||||
|
||||
async def run_async(self):
|
||||
"""Run the adapter - Teams uses webhook mode, so just keep alive and log webhook URL"""
|
||||
# Print webhook callback address
|
||||
if self.bot_uuid and hasattr(self.logger, 'ap'):
|
||||
try:
|
||||
api_port = self.logger.ap.instance_config.data['api']['port']
|
||||
webhook_url = f"http://127.0.0.1:{api_port}/bots/{self.bot_uuid}"
|
||||
webhook_url_public = f"http://<Your-Public-IP>:{api_port}/bots/{self.bot_uuid}"
|
||||
|
||||
await self.logger.info(f"Teams Bot Webhook URL:")
|
||||
await self.logger.info(f" Local: {webhook_url}")
|
||||
await self.logger.info(f" Public: {webhook_url_public}")
|
||||
await self.logger.info(f"Configure this URL as the messaging endpoint in Azure Bot Service")
|
||||
except Exception as e:
|
||||
await self.logger.warning(f"Could not generate webhook URL: {e}")
|
||||
|
||||
# Keep the adapter running
|
||||
async def keep_alive():
|
||||
while True:
|
||||
await asyncio.sleep(1)
|
||||
|
||||
await keep_alive()
|
||||
|
||||
async def kill(self) -> bool:
|
||||
"""Shutdown the adapter"""
|
||||
await self.logger.info('Teams adapter stopped')
|
||||
return True
|
||||
37
src/langbot/pkg/platform/sources/teams.yaml
Normal file
37
src/langbot/pkg/platform/sources/teams.yaml
Normal file
@@ -0,0 +1,37 @@
|
||||
apiVersion: v1
|
||||
kind: MessagePlatformAdapter
|
||||
metadata:
|
||||
name: teams
|
||||
label:
|
||||
en_US: Microsoft Teams
|
||||
zh_Hans: 微软 Teams
|
||||
description:
|
||||
en_US: Microsoft Teams Adapter - supports personal and channel/group chats
|
||||
zh_Hans: 微软 Teams 适配器,支持个人聊天和频道/群组聊天
|
||||
icon: teams.svg
|
||||
spec:
|
||||
config:
|
||||
- name: app_id
|
||||
label:
|
||||
en_US: App ID
|
||||
zh_Hans: 应用 ID
|
||||
description:
|
||||
en_US: Microsoft App ID from Azure Bot registration
|
||||
zh_Hans: Azure Bot 注册的 Microsoft 应用 ID
|
||||
type: string
|
||||
required: true
|
||||
default: ""
|
||||
- name: app_password
|
||||
label:
|
||||
en_US: App Password
|
||||
zh_Hans: 应用密码
|
||||
description:
|
||||
en_US: Microsoft App Password (Client Secret) from Azure Bot registration
|
||||
zh_Hans: Azure Bot 注册的 Microsoft 应用密码(客户端密钥)
|
||||
type: string
|
||||
required: true
|
||||
default: ""
|
||||
execution:
|
||||
python:
|
||||
path: ./teams.py
|
||||
attr: TeamsAdapter
|
||||
Reference in New Issue
Block a user