Commit Graph

1 Commits

  • Python: add agent-framework-hosting-activity-protocol channel (#5641)
    * feat(hosting-activity-protocol): rename Bot Framework channel to ActivityProtocolChannel
    
    The existing Bot-Framework-via-Azure-Bot-Service channel was previously
    shipped under the name ``hosting-teams`` / ``TeamsChannel``. That name
    is misleading for what the channel actually does -- it speaks the Bot
    Framework Activity Protocol against Azure Bot Service, which fans out
    across MS Teams, Slack, Webex, Telegram-via-Bot-Service, etc., and does
    not provide any Teams-specific affordances.
    
    This PR renames the package atomically and frees the ``hosting-teams``
    name for a future Teams-native channel built on
    ``microsoft-teams-apps`` (PR-5b, spec req #28).
    
    Renames (all in one commit):
    
    - Package: ``agent-framework-hosting-teams`` ->
      ``agent-framework-hosting-activity-protocol``
    - Module: ``agent_framework_hosting_teams`` ->
      ``agent_framework_hosting_activity_protocol``
    - Channel class: ``TeamsChannel`` -> ``ActivityProtocolChannel``
    - Helper: ``teams_isolation_key`` -> ``activity_protocol_isolation_key``
      (isolation key prefix ``teams:`` -> ``activity:``)
    - Channel name: ``"teams"`` -> ``"activity"``; default mount path
      ``/teams`` -> ``/activity``
    - Internal helper: ``_parse_teams_activity`` -> ``_parse_activity``
    - Worker task name + a couple of error strings updated for consistency
    
    Updates README.md and the module docstring to call out:
    
    - this is the channel-neutral Activity Protocol channel,
    - it surfaces what every Bot-Service-connected channel has in common
      (text in / text out),
    - a forthcoming ``agent-framework-hosting-teams`` package will layer
      Teams-specific affordances (adaptive cards, message extensions,
      dialogs, SSO, ...) on the same Bot Service transport.
    
    Workspace: registers ``agent-framework-hosting-activity-protocol`` in
    ``python/pyproject.toml`` and adds the matching pyright
    ``executionEnvironments`` entry.
    
    Behavior is unchanged. Pyright + mypy clean, 11 tests pass.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * review: address PR-5 round 2 feedback
    
    - security (#3198327004): add `service_url_allowed_hosts` constructor
      option (default `botframework.com` + `smba.trafficmanager.net`) and
      reject inbound activities whose `serviceUrl` host falls outside it
      with HTTP 400 — without this gate a malicious caller could redirect
      outbound replies (and the attached bearer token) to an
      attacker-controlled host
    - security (#3198324219): add `inbound_auth_validator` async callback;
      log a loud WARNING at startup when no validator AND no operator
      reverse-proxy is configured so the dev-mode bypass cannot
      accidentally ship to production. Document the contract: prototype
      intentionally does not ship JWT validation (out of scope); operators
      must plug a validator or terminate auth in front of the channel
    - retry semantics (#3198328746): distinguish transient outbound
      failures (httpx network errors, non-2xx from Bot Service) — return
      502 so Bot Service retries — from deterministic agent failures —
      return 200 so Bot Service does not retry the same broken activity
      in a loop
    - bug (#3198330424): fix the placeholder-failure deadlock. When
      `send_initial_placeholder` fails, `activity_id` stays `None`, the
      edit-worker loop exit condition (`accumulated == last_sent`) is
      unreachable while no PUT is possible, and the worker would deadlock
      on `wake.wait()` forever after `worker_done` is set. Now: skip the
      worker entirely on placeholder failure and POST a single final
      activity at the end with whatever accumulated
    - tests (#3198334465, #3187178091, #3198336045): add coverage for
      - `_is_service_url_allowed` allow/deny matrix + webhook 400 on
        disallowed serviceUrl
      - `inbound_auth_validator` allow/deny/raises paths
      - outbound `Authorization: Bearer <token>` header presence in
        production mode and absence in dev mode
      - the streaming path (`_stream_to_conversation`): placeholder +
        final edit, placeholder-failure fallback (with timeout guard
        against deadlock regression), and empty-stream `(no response)`
        placeholder replacement
      - retry-signal differentiation: outbound `httpx.ConnectError` →
        502; deterministic `ValueError` from the agent → 200
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * test(hosting): drop redundant @pytest.mark.asyncio decorators
    
    asyncio_mode = "auto" is configured in pyproject.toml across the
    hosting packages, so individual @pytest.mark.asyncio decorators are
    unnecessary.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * feat(hosting-activity-protocol): add response hooks
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * docs(hosting-activity-protocol): mark constructor keyword args
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    ---------
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>