Users/axsuarez/slack enchanced support#390
Draft
axelsrz wants to merge 2 commits into
Draft
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds initial Slack hosting support to the Microsoft Agents Python ecosystem by introducing a new microsoft-agents-hosting-slack package, plus accompanying tests, a runnable Slack sample, and CI pipeline integration to build/install the new wheel.
Changes:
- Introduces
microsoft-agents-hosting-slackwith Slack Web API client, streaming helper (SlackStream), Slack channel-data models, and routing extension (SlackAgentExtension). - Adds a Slack agent sample and a new
tests/hosting_slacksuite covering the new package behaviors. - Updates GitHub Actions and Azure DevOps pipelines to install the Slack hosting wheel during CI runs.
Reviewed changes
Copilot reviewed 35 out of 37 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/hosting_slack/test_slack_stream.py | Unit tests for SlackStream start/append/stop behavior and block parsing. |
| tests/hosting_slack/test_slack_response.py | Unit tests for SlackResponse parsing and dot-path navigation. |
| tests/hosting_slack/test_slack_helpers.py | Unit tests for Slack encode/decode and conversation-id helpers. |
| tests/hosting_slack/test_slack_channel_data.py | Unit tests for SlackChannelData, Events envelope/content models, and path navigation. |
| tests/hosting_slack/test_slack_api.py | Unit tests for SlackApi request serialization and error handling. |
| tests/hosting_slack/test_slack_agent_extension.py | Unit tests for Slack-scoped route registration and API delegation. |
| tests/hosting_slack/test_path_navigator.py | Unit tests for the dot/bracket path navigator utility. |
| tests/hosting_slack/init.py | Test package marker for Slack hosting tests. |
| test_samples/extensions/slack-agent/src/start_server.py | Aiohttp server bootstrap for the Slack sample agent. |
| test_samples/extensions/slack-agent/src/main.py | Slack sample entrypoint wiring logging + app startup. |
| test_samples/extensions/slack-agent/src/app.py | Slack sample app configuration (storage, adapter, auth). |
| test_samples/extensions/slack-agent/src/agent.py | Slack sample agent routes demonstrating Slack API + streaming + blocks. |
| test_samples/extensions/slack-agent/src/init.py | Slack sample package marker. |
| test_samples/extensions/slack-agent/requirements.txt | Slack sample dependencies. |
| test_samples/extensions/slack-agent/README.md | Slack sample usage and behavior documentation. |
| test_samples/extensions/slack-agent/env.TEMPLATE | Slack sample environment template for Bot Service credentials. |
| libraries/microsoft-agents-hosting-slack/setup.py | Slack hosting package setup (version + dependencies). |
| libraries/microsoft-agents-hosting-slack/readme.md | Slack hosting package README for PyPI/docs. |
| libraries/microsoft-agents-hosting-slack/pyproject.toml | Slack hosting package metadata and build configuration. |
| libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/slack_helpers.py | Slack formatting helpers + conversation-id parsing utilities. |
| libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/slack_agent_extension.py | Slack-specific route registration + helpers (call, create_stream). |
| libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/api/slack_stream.py | SlackStream implementation for chat.*Stream calls. |
| libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/api/slack_response.py | SlackResponse model + SlackResponseException. |
| libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/api/slack_model.py | Base SlackModel with dot-path get / try_get. |
| libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/api/slack_channel_data.py | SlackChannelData model bridging Bot Service Slack channelData. |
| libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/api/slack_api.py | SlackApi aiohttp client for Slack Web API calls. |
| libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/api/event_envelope.py | EventEnvelope model (outer Events API callback). |
| libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/api/event_content.py | EventContent model (inner event payload). |
| libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/api/chunks.py | Pydantic models for streaming chunk payload shapes. |
| libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/api/action_payload.py | ActionPayload model for interactive/block action callbacks. |
| libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/api/init.py | Public API exports for Slack hosting package. |
| libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/_path_navigator.py | Dot/bracket path navigation helper used by Slack models. |
| libraries/microsoft-agents-hosting-slack/microsoft_agents/hosting/slack/init.py | Top-level Slack hosting exports. |
| libraries/microsoft-agents-hosting-slack/MANIFEST.in | Ensures VERSION.txt is included in source distributions. |
| libraries/microsoft-agents-hosting-slack/LICENSE | MIT license file for the new package. |
| .github/workflows/python-package.yml | CI: installs the Slack hosting wheel during test runs. |
| .azdo/ci-pr.yaml | Azure DevOps CI: installs the Slack hosting wheel during test runs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+32
to
+38
| @property | ||
| def channel(self) -> Optional[str]: | ||
| """Slack channel id, sourced from the envelope or action payload.""" | ||
| if self.envelope is not None: | ||
| return self.envelope.get("event.channel") | ||
| if self.payload is not None: | ||
| return self.payload.get("channel") |
| if isinstance(value, dict): | ||
| return {k: _strip_nones(v) for k, v in value.items() if v is not None} | ||
| if isinstance(value, list): | ||
| return [_strip_nones(v) for v in value] |
Comment on lines
+86
to
+98
| if not chunks: | ||
| return self | ||
|
|
||
| await self._slack_api.call( | ||
| "chat.appendStream", | ||
| { | ||
| "channel": self._channel, | ||
| "ts": self._message_ts, | ||
| "thread_ts": self._thread_ts, | ||
| "chunks": [_chunk_to_dict(c) for c in chunks], | ||
| }, | ||
| self._token, | ||
| ) |
Comment on lines
+101
to
+151
| async def stop( | ||
| self, | ||
| chunks: Optional[Sequence[BaseModel]] = None, | ||
| blocks: Union[str, Sequence[Any], dict, None] = None, | ||
| ) -> None: | ||
| """Stop the active stream, optionally finalizing with chunks and/or | ||
| Block Kit blocks. | ||
|
|
||
| ``blocks`` may be a JSON-array string, a JSON-object string containing | ||
| a ``"blocks"`` array, a Python list of block dicts, or a dict with a | ||
| top-level ``"blocks"`` key. | ||
|
|
||
| See https://docs.slack.dev/reference/methods/chat.stopStream | ||
| """ | ||
| if not self._message_ts: | ||
| return | ||
|
|
||
| resolved_blocks = self._resolve_blocks(blocks) | ||
| resolved_chunks = ( | ||
| [_chunk_to_dict(c) for c in chunks] if chunks is not None else None | ||
| ) | ||
|
|
||
| body: dict[str, Any] = { | ||
| "channel": self._channel, | ||
| "ts": self._message_ts, | ||
| "thread_ts": self._thread_ts, | ||
| } | ||
| if resolved_chunks is not None: | ||
| body["chunks"] = resolved_chunks | ||
| if resolved_blocks is not None: | ||
| body["blocks"] = resolved_blocks | ||
|
|
||
| await self._slack_api.call("chat.stopStream", body, self._token) | ||
|
|
||
| @staticmethod | ||
| def _resolve_blocks(blocks: Any) -> Optional[list[Any]]: | ||
| if blocks is None: | ||
| return None | ||
| if isinstance(blocks, str): | ||
| try: | ||
| parsed = json.loads(blocks) | ||
| except json.JSONDecodeError as exc: | ||
| raise ValueError("blocks string is not valid JSON") from exc | ||
| return SlackStream._resolve_blocks(parsed) | ||
| if isinstance(blocks, dict): | ||
| if "blocks" not in blocks or not isinstance(blocks["blocks"], list): | ||
| raise ValueError("blocks object must contain a 'blocks' array property") | ||
| return blocks["blocks"] | ||
| if isinstance(blocks, list): | ||
| return blocks | ||
| raise ValueError( |
Comment on lines
+101
to
+122
| async def create_stream( | ||
| self, | ||
| turn_context: TurnContext, | ||
| thread_ts: Optional[str] = None, | ||
| ) -> SlackStream: | ||
| """Create and start a :class:`SlackStream` for the current Slack thread.""" | ||
| channel_data = SlackChannelData.from_activity(turn_context.activity) | ||
| if channel_data.envelope is None: | ||
| raise ValueError( | ||
| "create_stream requires a Slack event envelope on the activity" | ||
| ) | ||
| resolved_thread_ts = thread_ts or channel_data.envelope.get("event.ts") | ||
| api = self._slack_api | ||
| if turn_context.has(_SLACK_API_SERVICE_KEY): | ||
| api = turn_context.get(_SLACK_API_SERVICE_KEY) # type: ignore[assignment] | ||
| stream = SlackStream( | ||
| api, | ||
| channel_data.envelope.get("event.channel"), | ||
| resolved_thread_ts, | ||
| channel_data.api_token or "", | ||
| ) | ||
| return await stream.start() |
| """Remap caller-supplied path before navigation. Default: identity.""" | ||
| return path | ||
|
|
||
| def get(self, path: str, default: Optional[T] = None, type_: Type[T] = None) -> Any: |
| app["agent_app"] = agent_application | ||
| app["adapter"] = agent_application.adapter | ||
|
|
||
| run_app(app, host="localhost", port=environ.get("PORT", 3978)) |
Comment on lines
+8
to
+10
| import json | ||
| from contextlib import asynccontextmanager | ||
| from typing import Any |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request introduces the new
microsoft-agents-hosting-slackPython package, including its initial implementation, packaging, and integration into the CI/CD pipelines. The changes add Slack-specific hosting and API support to the Microsoft Agents ecosystem, following the established patterns for other hosting/storage packages.The most important changes are:
New Slack Hosting Package Implementation
microsoft-agents-hosting-slackpackage, including core modules for Slack integration such asslack_agent_extension, Slack API models, helpers, and path navigation utilities. This provides the foundational code for Slack bot hosting and API interaction. [1] [2] [3] [4] [5] [6] [7] [8] [9]Packaging and Licensing
LICENSE(MIT) andMANIFEST.in(includingVERSION.txt) to the new package to ensure proper open source compliance and packaging. [1] [2]CI/CD Integration
.azdo/ci-pr.yaml) and GitHub Actions (.github/workflows/python-package.yml) pipelines to build and install the newmicrosoft_agents_hosting_slackwheel, ensuring the Slack hosting package is built and tested alongside other components. [1] [2]