Commit Graph

3 Commits

  • Simplify Python hosting core (#6492)
    Remove linking, multicast, durable delivery, and host push machinery from the v1 hosting core. Keep those scenarios in a proposed follow-up ADR and update channel packages, samples, docs, tests, and workspace metadata around the smaller host/channel contract.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
  • Python: feat(python): cross-channel hosting improvements (endpoint paths, Activity push, Telegram/Teams fixes) (#6307)
    * Update hosting channel endpoint paths
    
    Treat channel paths as concrete endpoint paths so built-in channels can be mounted at their defaults or at the app root without sample-specific subclasses. Update docs, tests, and the Foundry Telegram Invocations sample accordingly.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * Add push support to ActivityProtocolChannel
    
    Implement the ChannelPush protocol so the Activity Protocol channel can
    receive cross-channel fan-out (ResponseTarget.all_linked) and echo_input
    replay as a non-originating destination:
    
    - Add push() that reconstructs a proactive Bot Framework activity (bot/user
      swap) from the stored conversation reference and POSTs it to
      /v3/conversations/{id}/activities.
    - Record a ChannelIdentity (service_url, conversation, bot, user, channel_id,
      locale) on ChannelRequest.identity so the host registers the channel under
      its isolation key for fan-out resolution.
    - Route the streaming path through deliver_response so Activity-originated
      turns broadcast like Telegram/Discord.
    - Add tests for push delivery, service_url validation, ChannelPush instance
      check, and inbound identity recording.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * Don't delete Telegram webhook on shutdown by default
    
    The TelegramChannel deleted its webhook on shutdown in webhook mode. During
    a rolling redeploy the new revision registers the webhook on startup, then
    the old revision's shutdown deletes it, silently breaking inbound delivery
    until the next boot. setWebhook is overwriting/idempotent, so startup
    re-asserts the webhook every boot and no teardown is needed.
    
    Add a delete_webhook_on_shutdown flag (default False) so teardown is opt-in
    for ephemeral deployments, and leave the webhook in place otherwise.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * Fix Activity channel streaming on non-Teams channels (405 on updateActivity)
    
    The Activity Protocol channel streamed replies the Teams way: POST a
    placeholder, then PUT-edit it as tokens arrive. Only Teams supports the
    updateActivity REST op; Web Chat, Direct Line and the Emulator return
    405 Method Not Allowed on the PUT, so the user saw only the placeholder.
    
    Gate the placeholder+edit flow on edit-capable channels (msteams). Other
    channels now buffer the stream and POST a single final message, mirroring
    the non-streaming path's fan-out and response-hook semantics. Also add a
    defensive 405 fallback inside the Teams edit loop so an unexpected 405
    can never strand the user on the placeholder.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * fix(hosting-activity-protocol): don't parse Teams inline attachment content as a URI
    
    Teams message activities include a text/html attachment whose inline
    `content` is raw HTML (not a URL). _parse_activity fell back to
    `attachment["content"]` and passed it to Content.from_uri, raising
    ContentError ("URI must contain a scheme") and failing the whole turn,
    so Teams users got no response.
    
    Only treat `contentUrl` as a URI, require an absolute scheme, and skip
    unparseable attachments defensively instead of failing the message.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * feat(hosting-activity-protocol): native slash-command dispatch for Teams/Activity
    
    Add a commands= parameter to ActivityProtocolChannel that intercepts a
    leading /command (after stripping the bot's own @mention) and dispatches
    to ChannelCommand handlers, mirroring the Telegram channel. Unknown
    commands fall through to the agent. The channel run_hook is applied to
    command requests so handlers observe the same resolved isolation key as
    ordinary messages, and handler errors are swallowed (200, no Bot Service
    retry of non-idempotent commands).
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * feat(hosting): silent attributed Telegram echoes + Teams markdown rendering
    
    - hosting-telegram: send cross-channel input echoes with disable_notification
      (silent) and detect echo payloads so they aren't re-broadcast.
    - hosting-activity-protocol: render outbound + push activities as textFormat
      'markdown' so Teams shows formatted replies (enables per-channel variants).
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * fix(hosting-activity-protocol): address PR #6307 review feedback
    
    Consult the host delivery pipeline even for empty streamed replies so
    ResponseTarget.none is honoured and non-originating fan-out is consulted
    instead of always emitting an originating "(no response)" message. Applies
    to both the progressive-edit (Teams) and buffered (Web Chat/Direct Line)
    streaming paths.
    
    Re-validate service_url against the allow-list in push(): the identity is
    read from a persisted store and push runs out-of-band, so the captured
    service_url must be re-checked before a bearer token is sent.
    
    Adds tests for empty-stream host consultation/suppression on both streaming
    paths and for push rejecting a disallowed service_url.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    ---------
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
  • Python: add hosting Channels sample apps (#5645)
    * samples(hosting): add hosting Channels sample apps under samples/04-hosting/af-hosting
    
    Adds five end-to-end sample apps under
    ``python/samples/04-hosting/af-hosting/`` that exercise the
    ``agent-framework-hosting`` Channels stack from the simplest single-channel
    case up to a multi-channel deployment with cross-channel identity linking.
    
    Samples (ordered by complexity)
    -------------------------------
    
    * ``foundry_hosted_agent/`` — minimal Responses + Invocations host with a
      Foundry-backed agent and ``FoundryHostedAgentHistoryProvider``.
      ``agd``-deployable; bundles a ``Dockerfile`` and
      ``scripts/vendor-packages.sh`` that copies workspace packages into
      ``_vendor/`` for self-contained builds. ``_vendor/`` is gitignored.
    * ``local_responses/`` — single-channel Responses host with a
      ``run_hook`` that strips caller-supplied options and forces a
      reasoning preset. Demonstrates the hook seam over the uniform
      ``ChannelRequest`` envelope.
    * ``local_responses_workflow/`` — Responses + Invocations exposing a
      three-agent workflow with per-conversation checkpoint storage.
    * ``local_telegram/`` — Responses + Telegram with a ``@tool``,
      ``FileHistoryProvider``, hooks, and a ``ResponseTarget`` multicast
      variant (``call_server_multicast.py``) that pushes a single Responses
      reply to a separate Telegram chat.
    * ``local_identity_link/`` — full surface: Responses + Invocations +
      Telegram + Activity Protocol (Teams) + the ``EntraIdentityLinkChannel``
      sidecar. Resolves per-channel ids onto a single Entra object id so a
      user's history follows them across surfaces.
    
    Notes
    -----
    
    * Samples that use Telegram/Teams via Activity Protocol depend on the
      renamed ``agent-framework-hosting-activity-protocol`` package (see the
      PR-5 series).
    * All samples use ``[tool.uv.sources]`` editable workspace deps, except
      ``foundry_hosted_agent/`` which uses the ``./_vendor/`` self-contained
      layout for ``azd`` Docker builds.
    * Each sample includes a ``README.md`` with run instructions and an
      ``app.py`` ASGI entrypoint plus a ``call_server.py`` client harness.
    
    Depends on the prior hosting PRs (foundry-hosted-agent refactor +
    hosting-core + the per-channel packages). After those merge, this
    branch can be rebased onto ``main`` cleanly.
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * samples(hosting): point sample deps at the feature/python-hosting GitHub branch
    
    Switches every sample's ``[tool.uv.sources]`` from in-monorepo
    editable path deps (which only resolve when running inside the
    agent-framework workspace) to git refs targeting the
    ``feature/python-hosting`` branch on
    ``microsoft/agent-framework``. Samples now install standalone outside
    the monorepo while the ``agent-framework-hosting*`` packages are still
    pre-PyPI; once they publish, the ``[tool.uv.sources]`` block can be
    dropped and the declared deps resolve from PyPI.
    
    Cleanup
    -------
    
    * Drops ``foundry_hosted_agent/scripts/vendor-packages.sh``,
      ``_vendor/`` from ``.gitignore``, the ``hooks.prepackage`` block in
      ``azure.yaml`` and the ``COPY _vendor/`` step in the Dockerfile —
      vendoring is no longer needed because git refs make the deps
      network-resolvable from any context.
    * Drops obsolete ``workspace.pyproject.toml`` reference and ``scripts/``
      / ``workspace.pyproject.toml`` entries from
      ``Dockerfile.dockerignore``.
    * Updates the foundry sample's Dockerfile to ``uv sync --no-dev``
      (no ``--frozen``) so it locks fresh against the GitHub-hosted deps
      at build time.
    * Drops every committed ``uv.lock`` because the resolver needs network
      access to ``feature/python-hosting`` to lock — they regenerate the
      first time a user runs ``uv sync`` after the branch lands.
    * Refreshes the per-sample READMEs to mention the GitHub install path
      instead of "in-tree workspace packages".
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * samples(hosting): address PR #5645 review comments
    
    - foundry_hosted_agent/call_server.py: replace hard-coded
      project_endpoint and service_session_id with FOUNDRY_PROJECT_ENDPOINT,
      FOUNDRY_HOSTED_AGENT_NAME, and optional FOUNDRY_HOSTED_SESSION_ID
      environment variables. Session-id is now optional so the sample
      exercises the new-conversation path by default.
    
    - local_identity_link/app.py:
      * make_telegram_hook: apply the reasoning bump regardless of
        identity-link state (the previous early-return on linked chats
        silently dropped the high-effort preset for the very flow the
        sample exists to demonstrate).
      * make_responses_hook: add a prominent DEV-ONLY warning that the
        client-supplied entra_oid shortcut bypasses identity verification
        and must be replaced by a JWT validator in production.
      * /link command: early-return when chat_id is missing instead of
        minting an authorize URL keyed on "telegram:None" (which would
        poison the link store with a binding any future chat_id-less
        update would collapse onto).
      * Switch ENTRA_CERT_PATH / ENTRA_CERT_PASSWORD env vars to the
        longer ENTRA_CERTIFICATE_PATH / ENTRA_CERTIFICATE_PASSWORD names
        that the README already documents.
      * channels: Sequence[Channel] -> list[Channel] (the next line
        appends, which a Sequence type doesn't expose).
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * chore(hosting-samples): apply sample formatting
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    * fix(hosting-samples): guard command input text
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
    
    ---------
    
    Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>