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>
This commit is contained in:
Eduard van Valkenburg
2026-06-03 16:37:03 +02:00
committed by GitHub
Unverified
parent e8c22caaeb
commit e5a6e35843
33 changed files with 1449 additions and 133 deletions
@@ -15,6 +15,7 @@ its own package (`agent-framework-hosting-responses`,
| [`local_responses/`](./local_responses) | The minimal shape: one agent + one `@tool` + `ResponsesChannel` + a single `run_hook` that strips caller-supplied options and forces a `reasoning` preset. | **Local only.** Start here to learn the run-hook seam. |
| [`local_responses_workflow/`](./local_responses_workflow) | A 4-step `Workflow` (typed `SloganBrief` intake → writer → legal → formatter) hosted behind **both** the Responses and Invocations channels via a shared `run_hook` that parses inbound text/JSON into the workflow's typed input. The host writes per-conversation checkpoints via `checkpoint_location=…`. Demonstrates workflow targets + structured input adaptation + multi-channel + resume-across-turns. Includes a `call_server.rest` file with REST examples for both endpoints. | **Local only.** |
| [`foundry_hosted_agent/`](./foundry_hosted_agent) | One Foundry agent, **Responses + Invocations only** — the minimal shape that is **runtime-compatible with the Foundry Hosted Agents platform**. | Ships with `Dockerfile` + `agent.yaml` + `agent.manifest.yaml` + `azure.yaml` so the same image runs locally **or** as a Foundry Hosted Agent (`azd up`). |
| [`foundry_telegram_invocations_weather/`](./foundry_telegram_invocations_weather) | Experimental Telegram weather bot that mounts `TelegramChannel` at `POST /invocations`, registers the Foundry Hosted Agents Invocations URL as the Telegram webhook, and uses `FoundryHostedAgentHistoryProvider` for storage. | Ships with `Dockerfile` + `agent.yaml` + `agent.manifest.yaml` + `azure.yaml`; used to validate whether a non-Responses channel can run under Foundry Invocations. |
| [`local_telegram/`](./local_telegram) | Adds Telegram, a `@tool`, `FileHistoryProvider`, run hooks (per-user / per-chat session keying), extra Telegram commands, and `ResponseTarget` multicast. Runs under Hypercorn with multiple workers. | **Local only.** No Dockerfile / Foundry packaging. |
| [`local_identity_link/`](./local_identity_link) | Everything in `local_telegram/` plus Teams and the Entra identity-link sidecar (`/auth/start` + `/auth/callback`). Demonstrates linking a Telegram chat to an Entra user so multiple non-Entra channels can share one isolation key. | **Local only.** No Dockerfile / Foundry packaging. |
@@ -3,7 +3,7 @@
Smallest end-to-end hosting sample. One Foundry-backed agent, two
channels, no human-chat surface — and that minimal shape is the whole
point: a host configured with at least the **Responses** and
**Invocations** channels under their default mount roots is
**Invocations** channels under their default endpoints is
**runtime-compatible with the Foundry Hosted Agents platform**. The
same container image runs locally, behind any ASGI server, or as a
Hosted Agent — no protocol shim, no extra adapter.
@@ -11,7 +11,7 @@ Hosted Agent — no protocol shim, no extra adapter.
| Route | Channel | Used by |
| ------------------------------ | -------------------- | ------------------------------------------- |
| `POST /responses` | `ResponsesChannel` | OpenAI Responses clients (`call_server.py`) |
| `POST /invocations/invoke` | `InvocationsChannel` | Host-native JSON envelope (Hosted Agents) |
| `POST /invocations` | `InvocationsChannel` | Host-native JSON envelope (Hosted Agents) |
## Conversation history
@@ -4,7 +4,7 @@
This sample is intentionally minimal and is **runtime-compatible with the
Foundry Hosted Agents platform**: a host that exposes the Responses and
Invocations channels under their default mount roots can be packaged as a
Invocations channels under their default endpoints can be packaged as a
container image and deployed to Foundry Hosted Agents without any protocol
shim. The same image runs locally, behind any ASGI server, or as a Hosted
Agent.
@@ -52,7 +52,7 @@ Run
Routes
------
- ``POST /responses`` — OpenAI Responses-shaped surface.
- ``POST /invocations/invoke`` — host-native JSON envelope.
- ``POST /invocations`` — host-native JSON envelope.
"""
from __future__ import annotations
@@ -3,7 +3,7 @@
"""Call the foundry_hosted_agent server three ways.
The foundry_hosted_agent host exposes ``POST /responses`` (OpenAI Responses-shaped) and
``POST /invocations/invoke`` (host-native), and that minimal contract is
``POST /invocations`` (host-native), and that minimal contract is
**runtime-compatible with the Foundry Hosted Agents platform** — so the same
agent code that calls the local server also calls the same image deployed
as a Hosted Agent.
@@ -0,0 +1,19 @@
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
WORKDIR /app
# The sample depends on hosting packages from Git refs until they publish to
# PyPI, so the remote builder needs git available during `uv sync`.
RUN apt-get update \
&& apt-get install -y --no-install-recommends git \
&& rm -rf /var/lib/apt/lists/*
COPY pyproject.toml ./
COPY app.py ./
RUN uv sync --no-dev
ENV PORT=8000
EXPOSE 8000
CMD ["uv", "run", "python", "app.py"]
@@ -0,0 +1,4 @@
*
!app.py
!pyproject.toml
!Dockerfile
@@ -0,0 +1,66 @@
# foundry_telegram_invocations_weather
Telegram weather bot sample for validating a non-Responses channel on Foundry
Hosted Agents. The sample configures `TelegramChannel(path="/invocations")` so
the webhook handler runs at the container endpoint `POST /invocations`; Foundry
exposes that route publicly as:
```text
{FOUNDRY_PROJECT_ENDPOINT}/agents/agent-framework-telegram-invocations-weather/endpoint/protocols/invocations?api-version=2025-11-15-preview
```
| Route | Channel | Used by |
|---|---|---|
| `POST /responses` | `ResponsesChannel` | Quick hosted-agent sanity checks |
| `POST /invocations` | `TelegramChannel` | Telegram webhook payloads |
The agent uses `FoundryHostedAgentHistoryProvider` and a small
`lookup_weather` tool so Telegram requests exercise model calls, tool calls,
and Foundry-hosted storage.
## Important platform note
This is an intentional experiment. Current Foundry Hosted Agents behavior
requires Entra bearer auth before a request reaches the container. Telegram
cannot attach that bearer token to webhook deliveries, so webhook registration
can succeed while live Telegram deliveries fail at the Foundry front door with
`401`. Authenticated calls to the Invocations endpoint are still useful for
validating the channel and storage behavior inside the container.
The sample does not configure `TELEGRAM_WEBHOOK_SECRET` because prior probing
showed Foundry strips Telegram's `X-Telegram-Bot-Api-Secret-Token` header before
the request reaches the container.
## Run locally
```bash
export FOUNDRY_PROJECT_ENDPOINT=https://<your-project>.services.ai.azure.com
export MODEL_DEPLOYMENT_NAME=gpt-5.4-nano
export TELEGRAM_BOT_TOKEN=<telegram-bot-token>
export TELEGRAM_WEBHOOK_URL=https://<public-local-tunnel>/invocations
az login
uv sync
uv run python app.py
```
## Deploy
```bash
set -a
. ../../../../.env
set +a
azd env set TELEGRAM_BOT_TOKEN "$TELEGRAM_BOT_TOKEN"
azd env set MODEL_DEPLOYMENT_NAME "${MODEL_DEPLOYMENT_NAME:-gpt-5.4-nano}"
azd env set HOSTING_INVOCATIONS_API_VERSION 2025-11-15-preview
azd up
```
If you connect this sample to an existing Foundry project instead of running
`azd provision`, make sure the azd environment has `AZURE_AI_PROJECT_ID` and the
project's ACR connection values set before running `azd deploy`.
On startup, `TelegramChannel` calls `setWebhook` using the Foundry public
Invocations URL derived from `FOUNDRY_PROJECT_ENDPOINT` and
`FOUNDRY_AGENT_NAME`.
@@ -0,0 +1,38 @@
name: agent-framework-telegram-invocations-weather
description: >
Telegram weather bot sample hosted by Agent Framework. The Telegram webhook
handler is mounted at /invocations so the Foundry Hosted Agents Invocations
protocol endpoint can be registered as the bot's webhook URL.
metadata:
tags:
- Agent Framework
- AI Agent Hosting
- Azure AI AgentServer
- Responses Protocol
- Invocations Protocol
- Telegram
template:
name: agent-framework-telegram-invocations-weather
kind: hosted
protocols:
- protocol: responses
version: 1.0.0
- protocol: invocations
version: 1.0.0
environment_variables:
- name: MODEL_DEPLOYMENT_NAME
value: "{{MODEL_DEPLOYMENT_NAME}}"
- name: TELEGRAM_BOT_TOKEN
value: "{{TELEGRAM_BOT_TOKEN}}"
- name: HOSTING_INVOCATIONS_API_VERSION
value: "{{HOSTING_INVOCATIONS_API_VERSION}}"
resources:
- kind: model
id: gpt-5.4-nano
name: MODEL_DEPLOYMENT_NAME
parameters:
properties:
- name: TELEGRAM_BOT_TOKEN
secret: true
- name: HOSTING_INVOCATIONS_API_VERSION
secret: false
@@ -0,0 +1,31 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml
kind: hosted
name: agent-framework-telegram-invocations-weather
description: |
Telegram weather bot sample hosted by Agent Framework. The Telegram webhook
handler is mounted at /invocations so the Foundry Hosted Agents Invocations
protocol endpoint can be registered as the bot's webhook URL.
metadata:
tags:
- Agent Framework
- AI Agent Hosting
- Azure AI AgentServer
- Responses Protocol
- Invocations Protocol
- Telegram
protocols:
- protocol: responses
version: 1.0.0
- protocol: invocations
version: 1.0.0
resources:
cpu: "1"
memory: 2Gi
environment_variables:
- name: MODEL_DEPLOYMENT_NAME
value: ${MODEL_DEPLOYMENT_NAME}
- name: TELEGRAM_BOT_TOKEN
value: ${TELEGRAM_BOT_TOKEN}
- name: HOSTING_INVOCATIONS_API_VERSION
value: ${HOSTING_INVOCATIONS_API_VERSION}
@@ -0,0 +1,194 @@
# Copyright (c) Microsoft. All rights reserved.
"""Telegram weather bot hosted behind Foundry Hosted Agents Invocations.
This sample intentionally mounts the Telegram webhook handler at the container's
``/invocations`` route so the Foundry public Invocations protocol URL can be
registered as the Telegram webhook URL:
``{FOUNDRY_PROJECT_ENDPOINT}/agents/{FOUNDRY_AGENT_NAME}/endpoint/protocols/invocations``
It uses ``FoundryHostedAgentHistoryProvider`` for conversation history and a
small weather tool to validate that a normal channel can run under the
Hosted Agents runtime. The sample also exposes Responses for a quick platform
sanity check.
Sample output after sending "weather in Amsterdam" to the Telegram bot:
Assistant:> Amsterdam is cloudy with a high of 16 C.
"""
from __future__ import annotations
import logging
import os
from dataclasses import replace
from typing import Annotated
from agent_framework import Agent, tool
from agent_framework.observability import enable_instrumentation
from agent_framework_foundry import FoundryChatClient
from agent_framework_foundry_hosting import FoundryHostedAgentHistoryProvider, foundry_response_id
from agent_framework_hosting import (
AgentFrameworkHost,
ChannelCommand,
ChannelCommandContext,
ChannelRequest,
)
from agent_framework_hosting_responses import ResponsesChannel
from agent_framework_hosting_telegram import TelegramChannel, telegram_isolation_key
from azure.identity.aio import DefaultAzureCredential
AGENT_NAME = "agent-framework-telegram-invocations-weather"
DEFAULT_MODEL_DEPLOYMENT = "gpt-5.4-nano"
DEFAULT_INVOCATIONS_API_VERSION = "2025-11-15-preview"
logging.basicConfig(
level=os.environ.get("LOG_LEVEL", "INFO").upper(),
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
for _noisy in (
"httpx",
"httpcore",
"azure.core.pipeline.policies.http_logging_policy",
"urllib3",
):
logging.getLogger(_noisy).setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
@tool(approval_mode="never_require")
def lookup_weather(location: Annotated[str, "The city to look up weather for."]) -> str:
"""Return a deterministic weather report for a city."""
reports = {
"seattle": "Seattle is rainy with a high of 12 C.",
"amsterdam": "Amsterdam is cloudy with a high of 16 C.",
"tokyo": "Tokyo is clear with a high of 22 C.",
"london": "London is misty with a high of 11 C.",
}
normalized = location.strip().lower()
return reports.get(normalized, f"{location} is sunny with a high of 20 C.")
def _foundry_invocations_webhook_url() -> str:
"""Build the public Foundry Invocations URL used as Telegram's webhook."""
explicit = os.environ.get("TELEGRAM_WEBHOOK_URL")
if explicit:
return explicit
project_endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"].rstrip("/")
agent_name = os.environ.get("FOUNDRY_AGENT_NAME", AGENT_NAME)
api_version = os.environ.get("HOSTING_INVOCATIONS_API_VERSION", DEFAULT_INVOCATIONS_API_VERSION)
return f"{project_endpoint}/agents/{agent_name}/endpoint/protocols/invocations?api-version={api_version}"
def _configure_observability() -> None:
"""Wire Azure Monitor OpenTelemetry when Foundry injects a connection string."""
conn_str = os.environ.get("APPLICATIONINSIGHTS_CONNECTION_STRING")
if not conn_str:
logger.info("APPLICATIONINSIGHTS_CONNECTION_STRING not set; skipping Azure Monitor export.")
return
from azure.monitor.opentelemetry import configure_azure_monitor # pyright: ignore[reportUnknownVariableType]
configure_azure_monitor(connection_string=conn_str)
logger.info("Azure Monitor OpenTelemetry configured.")
def telegram_hook(request: ChannelRequest, **_: object) -> ChannelRequest:
"""Clamp request options for Telegram-originating runs."""
options = dict(request.options or {})
options.pop("store", None)
options["reasoning"] = {"effort": "high", "summary": "auto"}
return replace(request, options=options)
def make_commands() -> list[ChannelCommand]:
"""Create Telegram slash commands used by the sample."""
async def handle_start(ctx: ChannelCommandContext) -> None:
await ctx.reply("Hi! Ask me for weather in Seattle, Amsterdam, Tokyo, London, or any city.")
async def handle_help(ctx: ChannelCommandContext) -> None:
await ctx.reply(
"/weather <city> - call the weather tool directly\n"
"/whoami - show your Telegram session key\n"
"/help - show this message"
)
async def handle_whoami(ctx: ChannelCommandContext) -> None:
await ctx.reply(f"Your session key is {telegram_isolation_key(ctx.request.attributes.get('chat_id'))}.")
async def handle_weather(ctx: ChannelCommandContext) -> None:
command_text = ctx.request.input if isinstance(ctx.request.input, str) else ""
_, _, location = command_text.partition(" ")
await ctx.reply(lookup_weather(location=(location.strip() or "Seattle")))
return [
ChannelCommand("start", "Introduce the bot", handle_start),
ChannelCommand("help", "List available commands", handle_help),
ChannelCommand("whoami", "Show the Telegram session key", handle_whoami),
ChannelCommand("weather", "Call the weather tool: /weather <city>", handle_weather),
]
def build_host() -> AgentFrameworkHost:
"""Build the Foundry-hosted Telegram weather agent."""
# 1. Create a shared credential for model calls and Foundry storage.
credential = DefaultAzureCredential()
project_endpoint = os.environ["FOUNDRY_PROJECT_ENDPOINT"]
# 2. Create the agent with a simple weather tool and Foundry-backed history.
agent = Agent(
client=FoundryChatClient(
project_endpoint=project_endpoint,
model=os.environ.get("MODEL_DEPLOYMENT_NAME", DEFAULT_MODEL_DEPLOYMENT),
credential=credential,
),
name="TelegramInvocationsWeatherAgent",
instructions=(
"You are a concise weather assistant. Use lookup_weather for weather questions "
"and answer in one short sentence."
),
tools=[lookup_weather],
context_providers=[
FoundryHostedAgentHistoryProvider(
credential=credential,
endpoint=project_endpoint,
),
],
)
# 3. Register Telegram at /invocations and keep Responses available for sanity checks.
return AgentFrameworkHost(
target=agent,
allow_in_process_runner=True,
channels=[
ResponsesChannel(response_id_factory=foundry_response_id),
TelegramChannel(
bot_token=os.environ["TELEGRAM_BOT_TOKEN"],
path="/invocations",
transport="webhook",
webhook_url=_foundry_invocations_webhook_url(),
parse_mode="Markdown",
commands=make_commands(),
run_hook=telegram_hook,
),
],
)
_configure_observability()
enable_instrumentation(enable_sensitive_data=True)
app = build_host().app
if __name__ == "__main__":
import asyncio
import hypercorn.asyncio
import hypercorn.config
config = hypercorn.config.Config()
config.bind = [f"0.0.0.0:{int(os.environ.get('PORT', '8000'))}"]
asyncio.run(hypercorn.asyncio.serve(app, config)) # type: ignore[arg-type]
@@ -0,0 +1,18 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json
requiredVersions:
extensions:
azure.ai.agents: '>=0.1.0-preview'
name: ai-foundry-telegram-invocations-weather
services:
agent-framework-telegram-invocations-weather:
project: .
host: azure.ai.agent
language: docker
docker:
remoteBuild: true
config:
container:
resources:
cpu: "1"
memory: 2Gi
@@ -0,0 +1,26 @@
[project]
name = "agent-framework-hosting-foundry-telegram-invocations-weather"
version = "0.0.1"
description = "Foundry Hosted Agents Telegram weather sample using the Invocations path."
requires-python = ">=3.10"
dependencies = [
"agent-framework-foundry",
"agent-framework-foundry-hosting",
"agent-framework-hosting",
"agent-framework-hosting-responses",
"agent-framework-hosting-telegram",
"azure-identity",
"aiohttp>=3.13.5",
"hypercorn>=0.17",
"mcp>=1.24,<2",
"azure-monitor-opentelemetry>=1.6",
]
[tool.uv]
package = false
[tool.uv.sources]
agent-framework-foundry-hosting = { git = "https://github.com/microsoft/agent-framework.git", branch = "feature/python-hosting", subdirectory = "python/packages/foundry_hosting" }
agent-framework-hosting = { git = "https://github.com/microsoft/agent-framework.git", branch = "feature/python-hosting", subdirectory = "python/packages/hosting" }
agent-framework-hosting-responses = { git = "https://github.com/microsoft/agent-framework.git", branch = "feature/python-hosting", subdirectory = "python/packages/hosting-responses" }
agent-framework-hosting-telegram = { git = "https://github.com/microsoft/agent-framework.git", branch = "feature/python-hosting", subdirectory = "python/packages/hosting-telegram" }
@@ -15,7 +15,7 @@ of the workflow.
`Workflow` target and dispatches to `workflow.run(...)` (no
`Agent.create_session(...)`).
- Two channels are mounted side-by-side (`ResponsesChannel` at
`/responses`, `InvocationsChannel` at `/invocations/invoke`). Both
`/responses`, `InvocationsChannel` at `/invocations`). Both
share the **same `brief_hook`** that **adapts the channel-native
input into the workflow start executor's typed input** — Responses
delivers a `list[Message]`, Invocations delivers a `str`, but the
@@ -45,7 +45,7 @@ Content-Type: application/json
###
# 4. Invocations API — structured brief
POST {{host}}/invocations/invoke
POST {{host}}/invocations
Content-Type: application/json
{
@@ -55,7 +55,7 @@ Content-Type: application/json
###
# 5. Invocations API — plain topic
POST {{host}}/invocations/invoke
POST {{host}}/invocations
Content-Type: application/json
{
@@ -66,7 +66,7 @@ Content-Type: application/json
###
# 6. Invocations API — resume the same session_id to reuse the
# workflow's per-conversation checkpoint store.
POST {{host}}/invocations/invoke
POST {{host}}/invocations
Content-Type: application/json
{
@@ -77,7 +77,7 @@ Content-Type: application/json
###
# 7. Invocations API — streaming (SSE; one `data:` line per chunk,
# terminated by `data: [DONE]`).
POST {{host}}/invocations/invoke
POST {{host}}/invocations
Content-Type: application/json
Accept: text/event-stream