Skip to content

Server

TAC server adapter for Hosted Agents in Foundry Agent Service. Requires the hosted-agents extra.

tac_microsoft.hosted_agents_server

TACHostedAgentsApp: TAC server for Hosted Agents in Foundry Agent Service.

Hosted Agents (the Foundry Agent Service runtime under azure-ai-agentserver-invocations) exposes exactly two endpoints — POST /invocations and WS /invocations_ws — bound to a fixed port, with sandbox affinity keyed on an agent_session_id query parameter set by APIM upstream. TACFastAPIServer's five-route layout doesn't fit that contract, so this server is a peer implementation that overloads the two fixed routes:

  • /invocations dispatches by payload shape:
  • Twilio Conversation Orchestrator JSON envelope → fan out to every registered channel's process_webhook(body, idempotency_token), matching TACFastAPIServer.conversation_webhook.
  • Twilio voice TwiML form (APIM converts form → JSON before this point; we detect by CallSid + absent eventType) → call voice_channel.handle_incoming_call(...) and return the TwiML XML.

  • /invocations_ws wraps the Starlette WebSocket in StarletteWebSocketAdapter and delegates to voice_channel.handle_websocket(...).

Auth (HMAC) is handled by APIM upstream, not here. The server validates that agent_session_id (set by APIM from the upstream request) matches the body's conversation/call ID for correctness — this is a sandbox-affinity invariant, not a security check. It also dedups CO retries by Twilio's i-twilio-idempotency-token header in a bounded LRU.

For voice, agent_session_id reaches Foundry on the /invocations_ws upgrade as a query string parameter. To make that happen, this server emits the wss URL with ?agent_session_id=<CallSid> already attached in the <ConversationRelay> TwiML, and additionally lists it as a <Parameter> child (Twilio CR appends those to the upgrade URL too — belt + suspenders so APIM has at least one place to pluck it from).

StarletteWebSocketAdapter

StarletteWebSocketAdapter(websocket: WebSocket)

Adapt a Starlette WebSocket to WebSocketProtocol.

Equivalent to tac.server.FastAPIWebSocketAdapter but for the raw Starlette WebSocket type that InvocationAgentServerHost exposes. Two Hosted-Agents specifics:

  • accept() is a no-op. InvocationAgentServerHost accepts the socket before the user handler runs; calling accept again raises. VoiceChannel.handle_websocket calls accept once at the top, so we silently swallow it.
  • WebSocketDisconnect (Starlette) is translated to WebSocketDisconnectError so VoiceChannel's framework-agnostic exception handling works.

TACHostedAgentsApp

TACHostedAgentsApp(
    tac: TAC,
    voice_channel: VoiceChannel | None = None,
    messaging_channels: list[MessagingChannel]
    | None = None,
    *,
    idempotency_cache_size: int = 4096,
)

TAC server hosted inside Hosted Agents in Foundry Agent Service.

Mirrors the public surface of TACFastAPIServer so it's a drop-in peer for users deploying to Foundry instead of Container Apps::

connector = AgentFrameworkConnector(tac=tac, create_agent=...)
server = TACHostedAgentsApp(
    tac=tac,
    voice_channel=connector.voice_channel,
    messaging_channels=[connector.sms_channel],
)
server.start()

Voice URL config is read from tac.config (a TACConfig): voice_public_domain should be the APIM hostname plus any path prefix the Twilio API points at (e.g. my-apim.azure-api.net/twilio). The wss URL emitted to Twilio is wss://{voice_public_domain}{voice_websocket_path}?agent_session_id={CallSid}; APIM rewrites that down to /invocations_ws on the Foundry backend.

Voice URL fields are read from tac.config (voice_public_domain / voice_websocket_path / voice_action_path). Configure a welcome greeting via VoiceChannelConfig.default_twiml_options on the voice channel.

start

start() -> None

Run the Hosted Agents server until exit.

InvocationAgentServerHost.run() binds to 0.0.0.0 on the port the platform passes via $PORT.