Skip to main content

ic_utils/interfaces/management_canister/
builders.rs

1//! Builder interfaces for some method calls of the management canister.
2
3#[doc(inline)]
4pub use super::attributes::{
5    ComputeAllocation, FreezingThreshold, LogMemoryLimit, MemoryAllocation, ReservedCyclesLimit,
6    WasmMemoryLimit,
7};
8use super::{ChunkHash, LogVisibility, ManagementCanister};
9use crate::call::CallFuture;
10use crate::{
11    call::AsyncCall, canister::Argument, interfaces::management_canister::MgmtMethod, Canister,
12};
13use async_trait::async_trait;
14use candid::{decode_args, utils::ArgumentEncoder, CandidType, Deserialize, Encode, Nat};
15use futures_util::{
16    future::ready,
17    stream::{self, FuturesUnordered},
18    FutureExt, Stream, StreamExt, TryStreamExt,
19};
20use ic_agent::{
21    agent::{CallResponse, EffectiveId},
22    export::Principal,
23    AgentError,
24};
25pub use ic_management_canister_types::{
26    CanisterInstallMode, CanisterSettings, EnvironmentVariable, InstallCodeArgs, UpgradeFlags,
27    WasmMemoryPersistence,
28};
29use sha2::{Digest, Sha256};
30use std::{
31    collections::BTreeSet,
32    convert::{From, TryInto},
33    future::IntoFuture,
34    pin::Pin,
35};
36
37/// A builder for a `create_canister` call.
38#[derive(Debug)]
39pub struct CreateCanisterBuilder<'agent, 'canister: 'agent> {
40    canister: &'canister Canister<'agent>,
41    /// For most use cases, this is `effective_canister_id`, the call will create a canister on the same subnet of it.
42    /// Subnet administrators can directly set effective_subnet_id and the canister will be created on the specified subnet.
43    effective_id: EffectiveId,
44    controllers: Option<Result<Vec<Principal>, AgentError>>,
45    compute_allocation: Option<Result<ComputeAllocation, AgentError>>,
46    memory_allocation: Option<Result<MemoryAllocation, AgentError>>,
47    freezing_threshold: Option<Result<FreezingThreshold, AgentError>>,
48    reserved_cycles_limit: Option<Result<ReservedCyclesLimit, AgentError>>,
49    wasm_memory_limit: Option<Result<WasmMemoryLimit, AgentError>>,
50    wasm_memory_threshold: Option<Result<WasmMemoryLimit, AgentError>>,
51    log_visibility: Option<Result<LogVisibility, AgentError>>,
52    log_memory_limit: Option<Result<LogMemoryLimit, AgentError>>,
53    environment_variables: Option<Result<Vec<EnvironmentVariable>, AgentError>>,
54    is_provisional_create: bool,
55    amount: Option<u128>,
56    specified_id: Option<Principal>,
57}
58
59impl<'agent, 'canister: 'agent> CreateCanisterBuilder<'agent, 'canister> {
60    /// Create an `CreateCanister` builder, which is also an `AsyncCall` implementation.
61    pub fn builder(canister: &'canister Canister<'agent>) -> Self {
62        Self {
63            canister,
64            effective_id: EffectiveId::Canister(Principal::management_canister()),
65            controllers: None,
66            compute_allocation: None,
67            memory_allocation: None,
68            freezing_threshold: None,
69            reserved_cycles_limit: None,
70            wasm_memory_limit: None,
71            wasm_memory_threshold: None,
72            log_visibility: None,
73            log_memory_limit: None,
74            environment_variables: None,
75            is_provisional_create: false,
76            amount: None,
77            specified_id: None,
78        }
79    }
80
81    /// Until developers can convert real ICP tokens to provision a new canister with cycles,
82    /// the system provides the `provisional_create_canister_with_cycles` method.
83    /// It behaves as `create_canister`, but initializes the canister’s balance with amount fresh cycles
84    /// (using `MAX_CANISTER_BALANCE` if amount = null, else capping the balance at `MAX_CANISTER_BALANCE`).
85    /// Cycles added to this call via `ic0.call_cycles_add` are returned to the caller.
86    /// This method is only available in local development instances, and will be removed in the future.
87    #[allow(clippy::wrong_self_convention)]
88    pub fn as_provisional_create_with_amount(self, amount: Option<u128>) -> Self {
89        Self {
90            is_provisional_create: true,
91            amount,
92            ..self
93        }
94    }
95
96    /// Specify the canister id.
97    ///
98    /// The effective canister id will also be set to the same value so that ic-ref can determine
99    /// the target subnet of this request. The replica implementation ignores it.
100    ///
101    /// This overwrites the effective id previously set by [`Self::with_effective_canister_id`] or
102    /// [`Self::with_effective_subnet_id`] (and is itself overwritten by any later call to those).
103    /// Only the last call among the three takes effect.
104    pub fn as_provisional_create_with_specified_id(self, specified_id: Principal) -> Self {
105        Self {
106            is_provisional_create: true,
107            specified_id: Some(specified_id),
108            effective_id: EffectiveId::Canister(specified_id),
109            ..self
110        }
111    }
112
113    /// Pass in an effective canister id for the update call.
114    ///
115    /// Boundary nodes use the effective {canister / subnet} id to route the request to the target subnet.
116    ///
117    /// This method, [`Self::with_effective_subnet_id`], and [`Self::as_provisional_create_with_specified_id`]
118    /// all write to the same underlying field, so only the last call among them takes effect; earlier
119    /// calls are silently shadowed.
120    pub fn with_effective_canister_id<C, E>(self, effective_canister_id: C) -> Self
121    where
122        E: std::fmt::Display,
123        C: TryInto<Principal, Error = E>,
124    {
125        match effective_canister_id.try_into() {
126            Ok(effective_canister_id) => Self {
127                effective_id: EffectiveId::Canister(effective_canister_id),
128                ..self
129            },
130            Err(_) => self,
131        }
132    }
133
134    /// Pass in an effective subnet id for the update call.
135    ///
136    /// The constructed `create_canister` call is only valid if the caller has subnet administrator permissions.
137    ///
138    /// Boundary nodes use the effective {canister / subnet} id to route the request to the target subnet.
139    ///
140    /// This method, [`Self::with_effective_canister_id`], and [`Self::as_provisional_create_with_specified_id`]
141    /// all write to the same underlying field, so only the last call among them takes effect; earlier
142    /// calls are silently shadowed.
143    pub fn with_effective_subnet_id<C, E>(self, effective_subnet_id: C) -> Self
144    where
145        E: std::fmt::Display,
146        C: TryInto<Principal, Error = E>,
147    {
148        match effective_subnet_id.try_into() {
149            Ok(subnet_id) => Self {
150                effective_id: EffectiveId::Subnet(subnet_id),
151                ..self
152            },
153            Err(_) => self,
154        }
155    }
156
157    /// Pass in a controller for the canister. Can be called multiple times to add multiple controllers.
158    pub fn with_controller<C, E>(self, controller: C) -> Self
159    where
160        E: std::fmt::Display,
161        C: TryInto<Principal, Error = E>,
162    {
163        let controller_result = controller
164            .try_into()
165            .map_err(|e| AgentError::MessageError(format!("{e}")));
166        let controllers = match (controller_result, self.controllers) {
167            (_, Some(Err(sticky))) => Some(Err(sticky)),
168            (Err(e), _) => Some(Err(e)),
169            (Ok(controller), Some(Ok(mut controllers))) => {
170                controllers.push(controller);
171                Some(Ok(controllers))
172            }
173            (Ok(controller), None) => Some(Ok(vec![controller])),
174        };
175        Self {
176            controllers,
177            ..self
178        }
179    }
180
181    /// Pass in a compute allocation value for the canister.
182    pub fn with_compute_allocation<C, E>(self, compute_allocation: C) -> Self
183    where
184        E: std::fmt::Display,
185        C: TryInto<ComputeAllocation, Error = E>,
186    {
187        Self {
188            compute_allocation: Some(
189                compute_allocation
190                    .try_into()
191                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
192            ),
193            ..self
194        }
195    }
196
197    /// Pass in a memory allocation value for the canister.
198    pub fn with_memory_allocation<C, E>(self, memory_allocation: C) -> Self
199    where
200        E: std::fmt::Display,
201        C: TryInto<MemoryAllocation, Error = E>,
202    {
203        Self {
204            memory_allocation: Some(
205                memory_allocation
206                    .try_into()
207                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
208            ),
209            ..self
210        }
211    }
212
213    /// Pass in a freezing threshold value for the canister.
214    pub fn with_freezing_threshold<C, E>(self, freezing_threshold: C) -> Self
215    where
216        E: std::fmt::Display,
217        C: TryInto<FreezingThreshold, Error = E>,
218    {
219        Self {
220            freezing_threshold: Some(
221                freezing_threshold
222                    .try_into()
223                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
224            ),
225            ..self
226        }
227    }
228
229    /// Pass in a reserved cycles limit value for the canister.
230    pub fn with_reserved_cycles_limit<C, E>(self, limit: C) -> Self
231    where
232        E: std::fmt::Display,
233        C: TryInto<ReservedCyclesLimit, Error = E>,
234    {
235        Self {
236            reserved_cycles_limit: Some(
237                limit
238                    .try_into()
239                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
240            ),
241            ..self
242        }
243    }
244
245    /// Pass in a Wasm memory limit value for the canister.
246    pub fn with_wasm_memory_limit<C, E>(self, wasm_memory_limit: C) -> Self
247    where
248        E: std::fmt::Display,
249        C: TryInto<WasmMemoryLimit, Error = E>,
250    {
251        Self {
252            wasm_memory_limit: Some(
253                wasm_memory_limit
254                    .try_into()
255                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
256            ),
257            ..self
258        }
259    }
260
261    /// Pass in a Wasm memory threshold value for the canister.
262    pub fn with_wasm_memory_threshold<C, E>(self, wasm_memory_threshold: C) -> Self
263    where
264        E: std::fmt::Display,
265        C: TryInto<WasmMemoryLimit, Error = E>,
266    {
267        Self {
268            wasm_memory_threshold: Some(
269                wasm_memory_threshold
270                    .try_into()
271                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
272            ),
273            ..self
274        }
275    }
276
277    /// Pass in a log memory limit value for the canister.
278    pub fn with_log_memory_limit<C, E>(self, log_memory_limit: C) -> Self
279    where
280        E: std::fmt::Display,
281        C: TryInto<LogMemoryLimit, Error = E>,
282    {
283        Self {
284            log_memory_limit: Some(
285                log_memory_limit
286                    .try_into()
287                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
288            ),
289            ..self
290        }
291    }
292
293    /// Pass in a log visibility setting for the canister.
294    pub fn with_log_visibility<C, E>(self, log_visibility: C) -> Self
295    where
296        E: std::fmt::Display,
297        C: TryInto<LogVisibility, Error = E>,
298    {
299        Self {
300            log_visibility: Some(
301                log_visibility
302                    .try_into()
303                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
304            ),
305            ..self
306        }
307    }
308
309    /// Pass in environment variables for the canister.
310    pub fn with_environment_variables<E, C>(self, environment_variables: C) -> Self
311    where
312        E: std::fmt::Display,
313        C: TryInto<Vec<EnvironmentVariable>, Error = E>,
314    {
315        Self {
316            environment_variables: Some(
317                environment_variables
318                    .try_into()
319                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
320            ),
321            ..self
322        }
323    }
324
325    /// Create an [`AsyncCall`] implementation that, when called, will create a
326    /// canister.
327    ///
328    /// Returns an error when the builder was configured with [`Self::with_effective_subnet_id`];
329    /// the subnet-scoped routing requires direct dispatch via [`Self::call`] or
330    /// [`Self::call_and_wait`] and is not expressible through the generic [`AsyncCall`] surface.
331    pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = (Principal,)>, AgentError> {
332        let prepared = self.prepare()?;
333        let effective_canister_id = match prepared.effective_id {
334            EffectiveId::Canister(p) => p,
335            EffectiveId::Subnet(_) => {
336                return Err(AgentError::MessageError(
337                    "CreateCanisterBuilder::build() does not support subnet routing; \
338                     use call()/call_and_wait() instead."
339                        .into(),
340                ));
341            }
342        };
343        Ok(prepared
344            .canister
345            .update(prepared.method)
346            .with_arg_raw(prepared.arg_bytes)
347            .with_effective_canister_id(effective_canister_id)
348            .build()
349            .map(|result: (CreateCanisterOut,)| (result.0.canister_id,)))
350    }
351
352    /// Make a call. This is equivalent to the [`AsyncCall::call`].
353    pub async fn call(self) -> Result<CallResponse<(Principal,)>, AgentError> {
354        match self.effective_id {
355            EffectiveId::Canister(_) => self.build()?.call().await,
356            EffectiveId::Subnet(subnet_id) => {
357                let prepared = self.prepare()?;
358                let response = subnet_call(
359                    prepared.canister,
360                    prepared.method,
361                    prepared.arg_bytes,
362                    subnet_id,
363                )
364                .await?;
365                match response {
366                    CallResponse::Response(bytes) => {
367                        let (out,): (CreateCanisterOut,) = decode_args(&bytes)
368                            .map_err(|e| AgentError::CandidError(Box::new(e)))?;
369                        Ok(CallResponse::Response((out.canister_id,)))
370                    }
371                    CallResponse::Poll(request_id) => Ok(CallResponse::Poll(request_id)),
372                }
373            }
374        }
375    }
376
377    /// Make a call. This is equivalent to the [`AsyncCall::call_and_wait`].
378    pub async fn call_and_wait(self) -> Result<(Principal,), AgentError> {
379        match self.effective_id {
380            EffectiveId::Canister(_) => self.build()?.call_and_wait().await,
381            EffectiveId::Subnet(subnet_id) => {
382                let prepared = self.prepare()?;
383                subnet_call_and_wait(
384                    prepared.canister,
385                    prepared.method,
386                    prepared.arg_bytes,
387                    subnet_id,
388                )
389                .await
390            }
391        }
392    }
393
394    fn prepare(self) -> Result<PreparedCreate<'agent, 'canister>, AgentError> {
395        let controllers = match self.controllers {
396            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
397            Some(Ok(x)) => Some(x),
398            None => None,
399        };
400        let compute_allocation = match self.compute_allocation {
401            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
402            Some(Ok(x)) => Some(Nat::from(u8::from(x))),
403            None => None,
404        };
405        let memory_allocation = match self.memory_allocation {
406            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
407            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
408            None => None,
409        };
410        let freezing_threshold = match self.freezing_threshold {
411            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
412            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
413            None => None,
414        };
415        let reserved_cycles_limit = match self.reserved_cycles_limit {
416            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
417            Some(Ok(x)) => Some(Nat::from(u128::from(x))),
418            None => None,
419        };
420        let wasm_memory_limit = match self.wasm_memory_limit {
421            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
422            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
423            None => None,
424        };
425        let wasm_memory_threshold = match self.wasm_memory_threshold {
426            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
427            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
428            None => None,
429        };
430        let log_visibility = match self.log_visibility {
431            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
432            Some(Ok(x)) => Some(x),
433            None => None,
434        };
435        let log_memory_limit = match self.log_memory_limit {
436            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
437            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
438            None => None,
439        };
440        let environment_variables = match self.environment_variables {
441            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
442            Some(Ok(x)) => Some(x),
443            None => None,
444        };
445
446        let (method, arg_bytes) = if self.is_provisional_create {
447            #[derive(CandidType)]
448            struct In {
449                amount: Option<Nat>,
450                settings: CanisterSettings,
451                specified_id: Option<Principal>,
452            }
453            let in_arg = In {
454                amount: self.amount.map(Nat::from),
455                settings: CanisterSettings {
456                    controllers,
457                    compute_allocation,
458                    memory_allocation,
459                    freezing_threshold,
460                    reserved_cycles_limit,
461                    wasm_memory_limit,
462                    wasm_memory_threshold,
463                    log_visibility,
464                    log_memory_limit,
465                    environment_variables,
466                },
467                specified_id: self.specified_id,
468            };
469            (
470                MgmtMethod::ProvisionalCreateCanisterWithCycles.as_ref(),
471                Encode!(&in_arg).map_err(|e| AgentError::CandidError(Box::new(e)))?,
472            )
473        } else {
474            let settings = CanisterSettings {
475                controllers,
476                compute_allocation,
477                memory_allocation,
478                freezing_threshold,
479                reserved_cycles_limit,
480                wasm_memory_limit,
481                wasm_memory_threshold,
482                log_visibility,
483                log_memory_limit,
484                environment_variables,
485            };
486            (
487                MgmtMethod::CreateCanister.as_ref(),
488                Encode!(&settings).map_err(|e| AgentError::CandidError(Box::new(e)))?,
489            )
490        };
491
492        Ok(PreparedCreate {
493            canister: self.canister,
494            effective_id: self.effective_id,
495            method,
496            arg_bytes,
497        })
498    }
499}
500
501#[derive(Deserialize, CandidType)]
502struct CreateCanisterOut {
503    canister_id: Principal,
504}
505
506struct PreparedCreate<'agent, 'canister> {
507    canister: &'canister Canister<'agent>,
508    effective_id: EffectiveId,
509    method: &'static str,
510    arg_bytes: Vec<u8>,
511}
512
513/// Submit a `create_canister` (or `provisional_create_canister_with_cycles`) call to the
514/// management canister via the subnet-scoped endpoint and decode the resulting canister id.
515async fn subnet_call_and_wait(
516    canister: &Canister<'_>,
517    method: &str,
518    arg_bytes: Vec<u8>,
519    subnet_id: Principal,
520) -> Result<(Principal,), AgentError> {
521    let agent = canister.agent;
522    let signed = agent
523        .update(&Principal::management_canister(), method)
524        .with_arg(arg_bytes)
525        .sign()?;
526    let effective_id = EffectiveId::Subnet(subnet_id);
527    let response = agent
528        .update_signed(effective_id, signed.signed_update)
529        .await?;
530    let bytes = match response {
531        CallResponse::Response(bytes) => bytes,
532        CallResponse::Poll(request_id) => {
533            let signed_status = agent.sign_request_status(effective_id, request_id)?;
534            agent
535                .wait_signed(
536                    &request_id,
537                    effective_id,
538                    signed_status.signed_request_status,
539                )
540                .await?
541                .0
542        }
543    };
544    let (out,): (CreateCanisterOut,) =
545        decode_args(&bytes).map_err(|e| AgentError::CandidError(Box::new(e)))?;
546    Ok((out.canister_id,))
547}
548
549/// Like [`subnet_call_and_wait`] but returns a [`CallResponse`] without polling, so that the
550/// caller can decide how to wait for the request to complete.
551async fn subnet_call(
552    canister: &Canister<'_>,
553    method: &str,
554    arg_bytes: Vec<u8>,
555    subnet_id: Principal,
556) -> Result<CallResponse<Vec<u8>>, AgentError> {
557    let agent = canister.agent;
558    let signed = agent
559        .update(&Principal::management_canister(), method)
560        .with_arg(arg_bytes)
561        .sign()?;
562    agent
563        .update_signed(EffectiveId::Subnet(subnet_id), signed.signed_update)
564        .await
565}
566
567#[cfg_attr(target_family = "wasm", async_trait(?Send))]
568#[cfg_attr(not(target_family = "wasm"), async_trait)]
569impl<'agent, 'canister: 'agent> AsyncCall for CreateCanisterBuilder<'agent, 'canister> {
570    type Value = (Principal,);
571
572    async fn call(self) -> Result<CallResponse<(Principal,)>, AgentError> {
573        self.call().await
574    }
575
576    async fn call_and_wait(self) -> Result<(Principal,), AgentError> {
577        self.call_and_wait().await
578    }
579}
580
581impl<'agent, 'canister: 'agent> IntoFuture for CreateCanisterBuilder<'agent, 'canister> {
582    type IntoFuture = CallFuture<'agent, (Principal,)>;
583    type Output = Result<(Principal,), AgentError>;
584
585    fn into_future(self) -> Self::IntoFuture {
586        Box::pin(self.call_and_wait())
587    }
588}
589
590#[doc(hidden)]
591#[deprecated(since = "0.42.0", note = "Please use UpgradeFlags instead")]
592pub type CanisterUpgradeOptions = UpgradeFlags;
593
594#[doc(hidden)]
595#[deprecated(since = "0.42.0", note = "Please use CanisterInstallMode instead")]
596pub type InstallMode = CanisterInstallMode;
597
598#[doc(hidden)]
599#[deprecated(since = "0.42.0", note = "Please use InstallCodeArgs instead")]
600pub type CanisterInstall = InstallCodeArgs;
601
602/// A builder for an `install_code` call.
603#[derive(Debug)]
604pub struct InstallCodeBuilder<'agent, 'canister: 'agent> {
605    canister: &'canister Canister<'agent>,
606    canister_id: Principal,
607    wasm: &'canister [u8],
608    arg: Argument,
609    mode: Option<CanisterInstallMode>,
610}
611
612impl<'agent, 'canister: 'agent> InstallCodeBuilder<'agent, 'canister> {
613    /// Create an `InstallCode` builder, which is also an `AsyncCall` implementation.
614    pub fn builder(
615        canister: &'canister Canister<'agent>,
616        canister_id: &Principal,
617        wasm: &'canister [u8],
618    ) -> Self {
619        Self {
620            canister,
621            canister_id: *canister_id,
622            wasm,
623            arg: Default::default(),
624            mode: None,
625        }
626    }
627
628    /// Set the argument to the installation, which will be passed to the init
629    /// method of the canister. Can be called at most once.
630    pub fn with_arg<Argument: CandidType>(
631        mut self,
632        arg: Argument,
633    ) -> InstallCodeBuilder<'agent, 'canister> {
634        self.arg.set_idl_arg(arg);
635        self
636    }
637    /// Set the argument with multiple arguments as tuple to the installation,
638    /// which will be passed to the init method of the canister. Can be called at most once.
639    pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
640        assert!(self.arg.0.is_none(), "argument is being set more than once");
641        self.arg = Argument::from_candid(tuple);
642        self
643    }
644    /// Set the argument passed in to the canister with raw bytes. Can be called at most once.
645    pub fn with_raw_arg(mut self, arg: Vec<u8>) -> InstallCodeBuilder<'agent, 'canister> {
646        self.arg.set_raw_arg(arg);
647        self
648    }
649
650    /// Pass in the [`CanisterInstallMode`].
651    pub fn with_mode(self, mode: CanisterInstallMode) -> Self {
652        Self {
653            mode: Some(mode),
654            ..self
655        }
656    }
657
658    /// Create an [`AsyncCall`] implementation that, when called, will install the
659    /// canister.
660    pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
661        Ok(self
662            .canister
663            .update(MgmtMethod::InstallCode.as_ref())
664            .with_arg(InstallCodeArgs {
665                mode: self.mode.unwrap_or(CanisterInstallMode::Install),
666                canister_id: self.canister_id,
667                wasm_module: self.wasm.to_owned(),
668                arg: self.arg.serialize()?,
669                sender_canister_version: None,
670            })
671            .with_effective_canister_id(self.canister_id)
672            .build())
673    }
674
675    /// Make a call. This is equivalent to the [`AsyncCall::call`].
676    pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
677        self.build()?.call().await
678    }
679
680    /// Make a call. This is equivalent to the [`AsyncCall::call_and_wait`].
681    pub async fn call_and_wait(self) -> Result<(), AgentError> {
682        self.build()?.call_and_wait().await
683    }
684}
685
686#[cfg_attr(target_family = "wasm", async_trait(?Send))]
687#[cfg_attr(not(target_family = "wasm"), async_trait)]
688impl<'agent, 'canister: 'agent> AsyncCall for InstallCodeBuilder<'agent, 'canister> {
689    type Value = ();
690
691    async fn call(self) -> Result<CallResponse<()>, AgentError> {
692        self.build()?.call().await
693    }
694
695    async fn call_and_wait(self) -> Result<(), AgentError> {
696        self.build()?.call_and_wait().await
697    }
698}
699
700impl<'agent, 'canister: 'agent> IntoFuture for InstallCodeBuilder<'agent, 'canister> {
701    type IntoFuture = CallFuture<'agent, ()>;
702    type Output = Result<(), AgentError>;
703
704    fn into_future(self) -> Self::IntoFuture {
705        AsyncCall::call_and_wait(self)
706    }
707}
708
709/// A builder for an `install_chunked_code` call.
710#[derive(Debug)]
711pub struct InstallChunkedCodeBuilder<'agent, 'canister> {
712    canister: &'canister Canister<'agent>,
713    target_canister: Principal,
714    store_canister: Option<Principal>,
715    chunk_hashes_list: Vec<ChunkHash>,
716    wasm_module_hash: Vec<u8>,
717    arg: Argument,
718    mode: CanisterInstallMode,
719}
720
721impl<'agent: 'canister, 'canister> InstallChunkedCodeBuilder<'agent, 'canister> {
722    /// Create an `InstallChunkedCodeBuilder`.
723    pub fn builder(
724        canister: &'canister Canister<'agent>,
725        target_canister: Principal,
726        wasm_module_hash: &[u8],
727    ) -> Self {
728        Self {
729            canister,
730            target_canister,
731            wasm_module_hash: wasm_module_hash.to_vec(),
732            store_canister: None,
733            chunk_hashes_list: vec![],
734            arg: Argument::new(),
735            mode: CanisterInstallMode::Install,
736        }
737    }
738
739    /// Set the chunks to install. These must previously have been set with [`ManagementCanister::upload_chunk`].
740    pub fn with_chunk_hashes(mut self, chunk_hashes: Vec<ChunkHash>) -> Self {
741        self.chunk_hashes_list = chunk_hashes;
742        self
743    }
744
745    /// Set the canister to pull uploaded chunks from. By default this is the same as the target canister.
746    pub fn with_store_canister(mut self, store_canister: Principal) -> Self {
747        self.store_canister = Some(store_canister);
748        self
749    }
750
751    /// Set the argument to the installation, which will be passed to the init
752    /// method of the canister. Can be called at most once.
753    pub fn with_arg(mut self, argument: impl CandidType) -> Self {
754        self.arg.set_idl_arg(argument);
755        self
756    }
757
758    /// Set the argument with multiple arguments as tuple to the installation,
759    /// which will be passed to the init method of the canister. Can be called at most once.
760    pub fn with_args(mut self, argument: impl ArgumentEncoder) -> Self {
761        assert!(self.arg.0.is_none(), "argument is being set more than once");
762        self.arg = Argument::from_candid(argument);
763        self
764    }
765
766    /// Set the argument passed in to the canister with raw bytes. Can be called at most once.
767    pub fn with_raw_arg(mut self, argument: Vec<u8>) -> Self {
768        self.arg.set_raw_arg(argument);
769        self
770    }
771
772    /// Set the [`CanisterInstallMode`].
773    pub fn with_install_mode(mut self, mode: CanisterInstallMode) -> Self {
774        self.mode = mode;
775        self
776    }
777
778    /// Create an [`AsyncCall`] implementation that, when called, will install the canister.
779    pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
780        #[derive(CandidType)]
781        struct In {
782            mode: CanisterInstallMode,
783            target_canister: Principal,
784            store_canister: Option<Principal>,
785            chunk_hashes_list: Vec<ChunkHash>,
786            wasm_module_hash: Vec<u8>,
787            arg: Vec<u8>,
788            sender_canister_version: Option<u64>,
789        }
790        let Self {
791            mode,
792            target_canister,
793            store_canister,
794            chunk_hashes_list,
795            wasm_module_hash,
796            arg,
797            ..
798        } = self;
799        Ok(self
800            .canister
801            .update(MgmtMethod::InstallChunkedCode.as_ref())
802            .with_arg(In {
803                mode,
804                target_canister,
805                store_canister,
806                chunk_hashes_list,
807                wasm_module_hash,
808                arg: arg.serialize()?,
809                sender_canister_version: None,
810            })
811            .with_effective_canister_id(target_canister)
812            .build())
813    }
814
815    /// Make the call. This is equivalent to [`AsyncCall::call`].
816    pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
817        self.build()?.call().await
818    }
819
820    /// Make the call. This is equivalent to [`AsyncCall::call_and_wait`].
821    pub async fn call_and_wait(self) -> Result<(), AgentError> {
822        self.build()?.call_and_wait().await
823    }
824}
825
826#[cfg_attr(target_family = "wasm", async_trait(?Send))]
827#[cfg_attr(not(target_family = "wasm"), async_trait)]
828impl<'agent, 'canister: 'agent> AsyncCall for InstallChunkedCodeBuilder<'agent, 'canister> {
829    type Value = ();
830
831    async fn call(self) -> Result<CallResponse<()>, AgentError> {
832        self.call().await
833    }
834
835    async fn call_and_wait(self) -> Result<(), AgentError> {
836        self.call_and_wait().await
837    }
838}
839
840impl<'agent, 'canister: 'agent> IntoFuture for InstallChunkedCodeBuilder<'agent, 'canister> {
841    type IntoFuture = CallFuture<'agent, ()>;
842    type Output = Result<(), AgentError>;
843
844    fn into_future(self) -> Self::IntoFuture {
845        AsyncCall::call_and_wait(self)
846    }
847}
848
849/// A builder for a [`ManagementCanister::install`] call. This automatically selects one-shot installation or chunked installation depending on module size.
850///
851/// # Warnings
852///
853/// This will clear chunked code storage if chunked installation is used. Do not use with canisters that you are manually uploading chunked code to.
854#[derive(Debug)]
855pub struct InstallBuilder<'agent, 'canister, 'builder> {
856    canister: &'canister ManagementCanister<'agent>,
857    canister_id: Principal,
858    // more precise lifetimes are used here at risk of annoying the user
859    // because `wasm` may be memory-mapped which is tricky to lifetime
860    wasm: &'builder [u8],
861    arg: Argument,
862    mode: CanisterInstallMode,
863}
864
865impl<'agent: 'canister, 'canister: 'builder, 'builder> InstallBuilder<'agent, 'canister, 'builder> {
866    // Messages are a maximum of 2MiB. Thus basic installation should cap the wasm and arg size at 1.85MiB, since
867    // the current API is definitely not going to produce 150KiB of framing data for it.
868    const CHUNK_CUTOFF: usize = (1.85 * 1024. * 1024.) as usize;
869
870    /// Create a canister installation builder.
871    pub fn builder(
872        canister: &'canister ManagementCanister<'agent>,
873        canister_id: &Principal,
874        wasm: &'builder [u8],
875    ) -> Self {
876        Self {
877            canister,
878            canister_id: *canister_id,
879            wasm,
880            arg: Default::default(),
881            mode: CanisterInstallMode::Install,
882        }
883    }
884
885    /// Set the argument to the installation, which will be passed to the init
886    /// method of the canister. Can be called at most once.
887    pub fn with_arg<Argument: CandidType>(mut self, arg: Argument) -> Self {
888        self.arg.set_idl_arg(arg);
889        self
890    }
891    /// Set the argument with multiple arguments as tuple to the installation,
892    /// which will be passed to the init method of the canister. Can be called at most once.
893    pub fn with_args(mut self, tuple: impl ArgumentEncoder) -> Self {
894        assert!(self.arg.0.is_none(), "argument is being set more than once");
895        self.arg = Argument::from_candid(tuple);
896        self
897    }
898    /// Set the argument passed in to the canister with raw bytes. Can be called at most once.
899    pub fn with_raw_arg(mut self, arg: Vec<u8>) -> Self {
900        self.arg.set_raw_arg(arg);
901        self
902    }
903
904    /// Pass in the [`CanisterInstallMode`].
905    pub fn with_mode(self, mode: CanisterInstallMode) -> Self {
906        Self { mode, ..self }
907    }
908
909    /// Invoke the installation process. This may result in many calls which may take several seconds;
910    /// use [`call_and_wait_with_progress`](Self::call_and_wait_with_progress) if you want progress reporting.
911    pub async fn call_and_wait(self) -> Result<(), AgentError> {
912        self.call_and_wait_with_progress()
913            .await
914            .try_for_each(|_| ready(Ok(())))
915            .await
916    }
917
918    /// Invoke the installation process. The returned stream must be iterated to completion; it is used to track progress,
919    /// as installation may take arbitrarily long, and is intended to be passed to functions like `indicatif::ProgressBar::wrap_stream`.
920    /// There are exactly [`size_hint().0`](Stream::size_hint) steps.
921    pub async fn call_and_wait_with_progress(
922        self,
923    ) -> impl Stream<Item = Result<(), AgentError>> + 'builder {
924        let stream_res = /* try { */ async move {
925            let arg = self.arg.serialize()?;
926            let stream: BoxStream<'_, _> =
927                if self.wasm.len() + arg.len() < Self::CHUNK_CUTOFF {
928                    Box::pin(
929                        async move {
930                            self.canister
931                                .install_code(&self.canister_id, self.wasm)
932                                .with_raw_arg(arg)
933                                .with_mode(self.mode)
934                                .call_and_wait()
935                                .await
936                        }
937                        .into_stream(),
938                    )
939                } else {
940                    let (existing_chunks,) = self.canister.stored_chunks(&self.canister_id).call_and_wait().await?;
941                    let existing_chunks = existing_chunks.into_iter().map(|c| c.hash).collect::<BTreeSet<_>>();
942                    let all_chunks = self.wasm.chunks(1024 * 1024).map(|x| (Sha256::digest(x).to_vec(), x)).collect::<Vec<_>>();
943                    let mut to_upload_chunks = vec![];
944                    for (hash, chunk) in &all_chunks {
945                        if !existing_chunks.contains(hash) {
946                            to_upload_chunks.push(*chunk);
947                        }
948                    }
949
950                    let upload_chunks_stream = FuturesUnordered::new();
951                    for chunk in to_upload_chunks {
952                        upload_chunks_stream.push(async move {
953                            let (_res,) = self
954                                .canister
955                                .upload_chunk(&self.canister_id, &ic_management_canister_types::UploadChunkArgs {
956                                    canister_id: self.canister_id,
957                                    chunk: chunk.to_vec(),
958                                })
959                                .call_and_wait()
960                                .await?;
961                            Ok(())
962                        });
963                    }
964                    let install_chunked_code_stream = async move {
965                        let results = all_chunks.iter().map(|(hash,_)| ChunkHash{ hash: hash.clone() }).collect();
966                        self.canister
967                            .install_chunked_code(
968                                &self.canister_id,
969                                &Sha256::digest(self.wasm),
970                            )
971                            .with_chunk_hashes(results)
972                            .with_raw_arg(arg)
973                            .with_install_mode(self.mode)
974                            .call_and_wait()
975                            .await
976                    }
977                    .into_stream();
978                    let clear_store_stream = async move {
979                        self.canister
980                            .clear_chunk_store(&self.canister_id)
981                            .call_and_wait()
982                            .await
983                    }
984                    .into_stream();
985
986                    Box::pin(
987                        upload_chunks_stream
988                            .chain(install_chunked_code_stream)
989                            .chain(clear_store_stream                        ),
990                    )
991                };
992            Ok(stream)
993        }.await;
994        match stream_res {
995            Ok(stream) => stream,
996            Err(err) => Box::pin(stream::once(async { Err(err) })),
997        }
998    }
999}
1000
1001impl<'agent: 'canister, 'canister: 'builder, 'builder> IntoFuture
1002    for InstallBuilder<'agent, 'canister, 'builder>
1003{
1004    type IntoFuture = CallFuture<'builder, ()>;
1005    type Output = Result<(), AgentError>;
1006
1007    fn into_future(self) -> Self::IntoFuture {
1008        Box::pin(self.call_and_wait())
1009    }
1010}
1011
1012/// A builder for an `update_settings` call.
1013#[derive(Debug)]
1014pub struct UpdateSettingsBuilder<'agent, 'canister: 'agent> {
1015    canister: &'canister Canister<'agent>,
1016    canister_id: Principal,
1017    controllers: Option<Result<Vec<Principal>, AgentError>>,
1018    compute_allocation: Option<Result<ComputeAllocation, AgentError>>,
1019    memory_allocation: Option<Result<MemoryAllocation, AgentError>>,
1020    freezing_threshold: Option<Result<FreezingThreshold, AgentError>>,
1021    reserved_cycles_limit: Option<Result<ReservedCyclesLimit, AgentError>>,
1022    wasm_memory_limit: Option<Result<WasmMemoryLimit, AgentError>>,
1023    wasm_memory_threshold: Option<Result<WasmMemoryLimit, AgentError>>,
1024    log_visibility: Option<Result<LogVisibility, AgentError>>,
1025    log_memory_limit: Option<Result<LogMemoryLimit, AgentError>>,
1026    environment_variables: Option<Result<Vec<EnvironmentVariable>, AgentError>>,
1027}
1028
1029impl<'agent, 'canister: 'agent> UpdateSettingsBuilder<'agent, 'canister> {
1030    /// Create an `UpdateCanister` builder, which is also an `AsyncCall` implementation.
1031    pub fn builder(canister: &'canister Canister<'agent>, canister_id: &Principal) -> Self {
1032        Self {
1033            canister,
1034            canister_id: *canister_id,
1035            controllers: None,
1036            compute_allocation: None,
1037            memory_allocation: None,
1038            freezing_threshold: None,
1039            reserved_cycles_limit: None,
1040            wasm_memory_limit: None,
1041            wasm_memory_threshold: None,
1042            log_visibility: None,
1043            log_memory_limit: None,
1044            environment_variables: None,
1045        }
1046    }
1047
1048    /// Pass in a controller for the canister. Can be called multiple times to add multiple controllers.
1049    pub fn with_controller<C, E>(self, controller: C) -> Self
1050    where
1051        E: std::fmt::Display,
1052        C: TryInto<Principal, Error = E>,
1053    {
1054        let controller_result = controller
1055            .try_into()
1056            .map_err(|e| AgentError::MessageError(format!("{e}")));
1057        let controllers = match (controller_result, self.controllers) {
1058            (_, Some(Err(sticky))) => Some(Err(sticky)),
1059            (Err(e), _) => Some(Err(e)),
1060            (Ok(controller), Some(Ok(mut controllers))) => {
1061                controllers.push(controller);
1062                Some(Ok(controllers))
1063            }
1064            (Ok(controller), None) => Some(Ok(vec![controller])),
1065        };
1066        Self {
1067            controllers,
1068            ..self
1069        }
1070    }
1071
1072    /// Pass in a compute allocation value for the canister.
1073    pub fn with_compute_allocation<C, E>(self, compute_allocation: C) -> Self
1074    where
1075        E: std::fmt::Display,
1076        C: TryInto<ComputeAllocation, Error = E>,
1077    {
1078        Self {
1079            compute_allocation: Some(
1080                compute_allocation
1081                    .try_into()
1082                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
1083            ),
1084            ..self
1085        }
1086    }
1087
1088    /// Pass in a memory allocation value for the canister.
1089    pub fn with_memory_allocation<C, E>(self, memory_allocation: C) -> Self
1090    where
1091        E: std::fmt::Display,
1092        C: TryInto<MemoryAllocation, Error = E>,
1093    {
1094        Self {
1095            memory_allocation: Some(
1096                memory_allocation
1097                    .try_into()
1098                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
1099            ),
1100            ..self
1101        }
1102    }
1103
1104    /// Pass in a freezing threshold value for the canister.
1105    pub fn with_freezing_threshold<C, E>(self, freezing_threshold: C) -> Self
1106    where
1107        E: std::fmt::Display,
1108        C: TryInto<FreezingThreshold, Error = E>,
1109    {
1110        Self {
1111            freezing_threshold: Some(
1112                freezing_threshold
1113                    .try_into()
1114                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
1115            ),
1116            ..self
1117        }
1118    }
1119
1120    /// Pass in a reserved cycles limit value for the canister.
1121    pub fn with_reserved_cycles_limit<C, E>(self, limit: C) -> Self
1122    where
1123        E: std::fmt::Display,
1124        C: TryInto<ReservedCyclesLimit, Error = E>,
1125    {
1126        Self {
1127            reserved_cycles_limit: Some(
1128                limit
1129                    .try_into()
1130                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
1131            ),
1132            ..self
1133        }
1134    }
1135
1136    /// Pass in a Wasm memory limit value for the canister.
1137    pub fn with_wasm_memory_limit<C, E>(self, wasm_memory_limit: C) -> Self
1138    where
1139        E: std::fmt::Display,
1140        C: TryInto<WasmMemoryLimit, Error = E>,
1141    {
1142        Self {
1143            wasm_memory_limit: Some(
1144                wasm_memory_limit
1145                    .try_into()
1146                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
1147            ),
1148            ..self
1149        }
1150    }
1151
1152    /// Pass in a Wasm memory threshold value for the canister.
1153    pub fn with_wasm_memory_threshold<C, E>(self, wasm_memory_threshold: C) -> Self
1154    where
1155        E: std::fmt::Display,
1156        C: TryInto<WasmMemoryLimit, Error = E>,
1157    {
1158        Self {
1159            wasm_memory_threshold: Some(
1160                wasm_memory_threshold
1161                    .try_into()
1162                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
1163            ),
1164            ..self
1165        }
1166    }
1167
1168    /// Pass in a log memory limit value for the canister.
1169    pub fn with_log_memory_limit<C, E>(self, log_memory_limit: C) -> Self
1170    where
1171        E: std::fmt::Display,
1172        C: TryInto<LogMemoryLimit, Error = E>,
1173    {
1174        Self {
1175            log_memory_limit: Some(
1176                log_memory_limit
1177                    .try_into()
1178                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
1179            ),
1180            ..self
1181        }
1182    }
1183
1184    /// Pass in a log visibility setting for the canister.
1185    pub fn with_log_visibility<C, E>(self, log_visibility: C) -> Self
1186    where
1187        E: std::fmt::Display,
1188        C: TryInto<LogVisibility, Error = E>,
1189    {
1190        Self {
1191            log_visibility: Some(
1192                log_visibility
1193                    .try_into()
1194                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
1195            ),
1196            ..self
1197        }
1198    }
1199
1200    /// Pass in environment variables for the canister.
1201    pub fn with_environment_variables<C, E>(self, environment_variables: C) -> Self
1202    where
1203        E: std::fmt::Display,
1204        C: TryInto<Vec<EnvironmentVariable>, Error = E>,
1205    {
1206        Self {
1207            environment_variables: Some(
1208                environment_variables
1209                    .try_into()
1210                    .map_err(|e| AgentError::MessageError(format!("{e}"))),
1211            ),
1212            ..self
1213        }
1214    }
1215
1216    /// Create an [`AsyncCall`] implementation that, when called, will update a
1217    /// canisters settings.
1218    pub fn build(self) -> Result<impl 'agent + AsyncCall<Value = ()>, AgentError> {
1219        #[derive(CandidType)]
1220        struct In {
1221            canister_id: Principal,
1222            settings: CanisterSettings,
1223        }
1224
1225        let controllers = match self.controllers {
1226            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1227            Some(Ok(x)) => Some(x),
1228            None => None,
1229        };
1230        let compute_allocation = match self.compute_allocation {
1231            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1232            Some(Ok(x)) => Some(Nat::from(u8::from(x))),
1233            None => None,
1234        };
1235        let memory_allocation = match self.memory_allocation {
1236            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1237            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1238            None => None,
1239        };
1240        let freezing_threshold = match self.freezing_threshold {
1241            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1242            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1243            None => None,
1244        };
1245        let reserved_cycles_limit = match self.reserved_cycles_limit {
1246            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1247            Some(Ok(x)) => Some(Nat::from(u128::from(x))),
1248            None => None,
1249        };
1250        let wasm_memory_limit = match self.wasm_memory_limit {
1251            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1252            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1253            None => None,
1254        };
1255        let wasm_memory_threshold = match self.wasm_memory_threshold {
1256            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1257            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1258            None => None,
1259        };
1260        let log_visibility = match self.log_visibility {
1261            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1262            Some(Ok(x)) => Some(x),
1263            None => None,
1264        };
1265        let log_memory_limit = match self.log_memory_limit {
1266            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1267            Some(Ok(x)) => Some(Nat::from(u64::from(x))),
1268            None => None,
1269        };
1270        let environment_variables = match self.environment_variables {
1271            Some(Err(x)) => return Err(AgentError::MessageError(format!("{x}"))),
1272            Some(Ok(x)) => Some(x),
1273            None => None,
1274        };
1275
1276        Ok(self
1277            .canister
1278            .update(MgmtMethod::UpdateSettings.as_ref())
1279            .with_arg(In {
1280                canister_id: self.canister_id,
1281                settings: CanisterSettings {
1282                    controllers,
1283                    compute_allocation,
1284                    memory_allocation,
1285                    freezing_threshold,
1286                    reserved_cycles_limit,
1287                    wasm_memory_limit,
1288                    wasm_memory_threshold,
1289                    log_visibility,
1290                    log_memory_limit,
1291                    environment_variables,
1292                },
1293            })
1294            .with_effective_canister_id(self.canister_id)
1295            .build())
1296    }
1297
1298    /// Make a call. This is equivalent to the [`AsyncCall::call`].
1299    pub async fn call(self) -> Result<CallResponse<()>, AgentError> {
1300        self.build()?.call().await
1301    }
1302
1303    /// Make a call. This is equivalent to the [`AsyncCall::call_and_wait`].
1304    pub async fn call_and_wait(self) -> Result<(), AgentError> {
1305        self.build()?.call_and_wait().await
1306    }
1307}
1308
1309#[cfg_attr(target_family = "wasm", async_trait(?Send))]
1310#[cfg_attr(not(target_family = "wasm"), async_trait)]
1311impl<'agent, 'canister: 'agent> AsyncCall for UpdateSettingsBuilder<'agent, 'canister> {
1312    type Value = ();
1313    async fn call(self) -> Result<CallResponse<()>, AgentError> {
1314        self.build()?.call().await
1315    }
1316
1317    async fn call_and_wait(self) -> Result<(), AgentError> {
1318        self.build()?.call_and_wait().await
1319    }
1320}
1321
1322impl<'agent, 'canister: 'agent> IntoFuture for UpdateSettingsBuilder<'agent, 'canister> {
1323    type IntoFuture = CallFuture<'agent, ()>;
1324    type Output = Result<(), AgentError>;
1325    fn into_future(self) -> Self::IntoFuture {
1326        AsyncCall::call_and_wait(self)
1327    }
1328}
1329
1330#[cfg(not(target_family = "wasm"))]
1331type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + Send + 'a>>;
1332#[cfg(target_family = "wasm")]
1333type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + 'a>>;