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