Skip to main content

livekit_api/services/
agent_dispatch.rs

1// Copyright 2025 LiveKit, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use super::{twirp_client::TwirpClient, ServiceBase, ServiceResult, LIVEKIT_PACKAGE};
16use crate::{
17    access_token::{AccessTokenError, VideoGrants},
18    get_env_keys,
19};
20use http::header::HeaderMap;
21use livekit_protocol as proto;
22
23const SVC: &str = "AgentDispatchService";
24
25#[derive(Debug)]
26pub struct AgentDispatchClient {
27    base: ServiceBase,
28    client: TwirpClient,
29}
30
31impl AgentDispatchClient {
32    /// Authenticates with an API key and secret, signing a short-lived token per request.
33    pub fn with_api_key(host: &str, api_key: &str, api_secret: &str) -> Self {
34        Self::build(
35            host,
36            ServiceBase::with_api_key(api_key, api_secret),
37            crate::http_client::Client::new(),
38        )
39    }
40
41    /// Authenticates with a pre-signed token, sent verbatim on every request.
42    pub fn with_token(host: &str, token: &str) -> Self {
43        Self::build(host, ServiceBase::with_token(token), crate::http_client::Client::new())
44    }
45
46    /// Builds the client from an already-constructed HTTP client so the unified
47    /// [`LiveKitApi`](super::LiveKitApi) can share one connection pool across services.
48    pub(crate) fn build(host: &str, base: ServiceBase, client: crate::http_client::Client) -> Self {
49        Self { base, client: TwirpClient::with_client(host, LIVEKIT_PACKAGE, None, client) }
50    }
51
52    #[cfg(test)]
53    pub(crate) fn with_default_headers(mut self, headers: http::HeaderMap) -> Self {
54        self.client = self.client.with_default_headers(headers);
55        self
56    }
57
58    /// Reads the API key and secret from the `LIVEKIT_API_KEY` and
59    /// `LIVEKIT_API_SECRET` environment variables.
60    pub fn new(host: &str) -> ServiceResult<Self> {
61        let (api_key, api_secret) = get_env_keys()?;
62        Ok(Self::with_api_key(host, &api_key, &api_secret))
63    }
64
65    /// Enables or disables region failover (enabled by default). Failover only
66    /// engages for LiveKit Cloud hosts.
67    pub fn with_failover(mut self, enabled: bool) -> Self {
68        self.client = self.client.with_failover(enabled);
69        self
70    }
71
72    /// Overrides the default per-request timeout (10s) for calls on this client.
73    pub fn with_request_timeout(mut self, timeout: std::time::Duration) -> Self {
74        self.client = self.client.with_request_timeout(timeout);
75        self
76    }
77
78    /// Creates an explicit dispatch for an agent to join a room.
79    ///
80    /// To use explicit dispatch, your agent must be registered with an `agent_name`.
81    ///
82    /// # Arguments
83    /// * `req` - Request containing dispatch creation parameters. The request can include
84    ///   an optional `deployment` field to target a specific agent deployment.
85    ///   Leave empty to target the production deployment.
86    ///
87    /// # Returns
88    /// The created agent dispatch object
89    ///
90    pub async fn create_dispatch(
91        &self,
92        req: proto::CreateAgentDispatchRequest,
93    ) -> ServiceResult<proto::AgentDispatch> {
94        const METHOD: &str = "CreateDispatch";
95        let headers = self.auth_headers(req.room.to_string())?;
96        Ok(self.client.request(SVC, METHOD, req, headers).await?)
97    }
98
99    /// Deletes an explicit dispatch for an agent in a room.
100    ///
101    /// # Arguments
102    /// * `dispatch_id` - ID of the dispatch to delete
103    /// * `room_name` - Name of the room containing the dispatch
104    ///
105    /// # Returns
106    /// The deleted agent dispatch object
107    ///
108    pub async fn delete_dispatch(
109        &self,
110        dispatch_id: impl Into<String>,
111        room_name: impl Into<String>,
112    ) -> ServiceResult<proto::AgentDispatch> {
113        const METHOD: &str = "DeleteDispatch";
114        let req = proto::DeleteAgentDispatchRequest {
115            dispatch_id: dispatch_id.into(),
116            room: room_name.into(),
117        };
118        let headers = self.auth_headers(req.room.to_string())?;
119        Ok(self.client.request(SVC, METHOD, req, headers).await?)
120    }
121
122    /// Lists all agent dispatches in a room.
123    ///
124    /// # Arguments
125    /// * `room_name` - Name of the room to list dispatches from
126    ///
127    /// # Returns
128    /// List of dispatch objects in the room
129    ///
130    pub async fn list_dispatch(
131        &self,
132        room_name: impl Into<String>,
133    ) -> ServiceResult<Vec<proto::AgentDispatch>> {
134        const METHOD: &str = "ListDispatch";
135        let req = proto::ListAgentDispatchRequest { room: room_name.into(), ..Default::default() };
136        let headers = self.auth_headers(req.room.to_string())?;
137        let res: proto::ListAgentDispatchResponse =
138            self.client.request(SVC, METHOD, req, headers).await?;
139        Ok(res.agent_dispatches)
140    }
141
142    /// Gets an agent dispatch by ID.
143    ///
144    /// # Arguments
145    /// * `dispatch_id` - ID of the dispatch to retrieve
146    /// * `room_name` - Name of the room containing the dispatch
147    ///
148    /// # Returns
149    /// Requested dispatch object if found, `None` otherwise
150    ///
151    pub async fn get_dispatch(
152        &self,
153        dispatch_id: impl Into<String>,
154        room_name: impl Into<String>,
155    ) -> ServiceResult<Option<proto::AgentDispatch>> {
156        const METHOD: &str = "ListDispatch";
157        let req = proto::ListAgentDispatchRequest {
158            room: room_name.into(),
159            dispatch_id: dispatch_id.into(),
160        };
161        let headers = self.auth_headers(req.room.to_string())?;
162        let mut res: proto::ListAgentDispatchResponse =
163            self.client.request(SVC, METHOD, req, headers).await?;
164        Ok(res.agent_dispatches.pop())
165    }
166}
167
168impl AgentDispatchClient {
169    /// Generates the auth header common to all dispatch request types.
170    fn auth_headers(&self, room: String) -> Result<HeaderMap, AccessTokenError> {
171        self.base.auth_header(VideoGrants { room, room_admin: true, ..Default::default() }, None)
172    }
173}