Skip to main content

Agent

Struct Agent 

Source
pub struct Agent { /* private fields */ }
Expand description

High-level AT Protocol agent.

Auth state lives in a single RwLock<Option<Session>>. The XRPC client is never mutated after construction — auth headers are passed per-request. This avoids token leaks, giant-lock contention, and split-lock atomicity gaps that arise from storing auth in the client’s default headers.

§Transparent refresh

Every XRPC call goes through xrpc_query_with_refresh / xrpc_procedure_with_refresh, which detect 401 / ExpiredToken responses, call Agent::refresh_session, and retry once. Concurrent refresh attempts are deduplicated via an async Mutex so N in-flight calls that all see an expired token issue exactly one /refreshSession request. If the refresh itself fails, the agent fires AtpSessionEvent::Expired and the original error propagates.

Implementations§

Source§

impl Agent

Source

pub fn new(service: impl AsRef<str>) -> Result<Self, AgentError>

Create a new agent pointing at the given service URL.

Available whenever XrpcClient::new is — native requires fetch-reqwest; on wasm the default is always the browser fetch backend.

Source

pub fn on_session<F>(&self, callback: F)
where F: Fn(AtpSessionEvent, Option<&Session>) + Send + Sync + 'static,

Register a session-event listener.

Returns (), not a handle — listener unregistration isn’t currently supported (the typical pattern is to register a single persistence callback that lives for the Agent’s lifetime). Multiple listeners are fired in registration order.

Source

pub fn service(&self) -> String

Get the service URL string.

Source

pub async fn did(&self) -> Option<Did>

Get the current session’s DID, if logged in.

Source

pub async fn session(&self) -> Option<Session>

Get the current session, if any.

Source

pub async fn anon_call_options(&self) -> Option<CallOptions>

Build anonymous CallOptions carrying just the proxy and labeler config, for methods that don’t need auth.

Exposed in case callers drive XrpcClient::query / ::procedure directly and want the agent’s proxy / labeler headers folded in.

Source

pub async fn configure_proxy(&self, target: Option<&str>)

Configure the service-proxy target (atproto-proxy header) for every subsequent call. Pass None to clear.

The canonical use case is chat, which runs on a different service: agent.configure_proxy(Some("did:web:api.bsky.chat#bsky_chat")).

Source

pub async fn with_proxy(&self, target: &str) -> Self

Return a new Agent configured with the given proxy target. Shares session state with this agent (cheap clone of internals).

Source

pub async fn configure_labelers(&self, labelers: &[LabelerOpts])

Configure the set of labelers sent as atproto-accept-labelers. Passing an empty slice clears the header.

Source

pub async fn login( &self, identifier: &AtIdentifier, password: &str, ) -> Result<Session, AgentError>

Log in with identifier (handle or DID) and password.

Emits AtpSessionEvent::Create on success, or AtpSessionEvent::CreateFailed if the server rejected the credentials.

Source

pub async fn resume_session(&self, session: Session) -> Result<(), AgentError>

Resume an existing session.

Verifies the session with the server before updating internal state. If verification fails, the agent remains unauthenticated.

Source

pub async fn refresh_session(&self) -> Result<Session, AgentError>

Refresh the current session tokens.

Emits AtpSessionEvent::Update on success or AtpSessionEvent::Expired if the refresh token was rejected. Uses a per-request header for the refresh call so the refresh JWT is never exposed as the global auth state. The new session is committed atomically in a single write lock.

Source

pub async fn post( &self, text: &str, facets: Option<Vec<Facet>>, created_at: Option<&str>, ) -> Result<Value, AgentError>

Create a new post.

If created_at is None, the current time is used.

Source

pub async fn post_rich( &self, rt: &RichText, created_at: Option<&str>, ) -> Result<Value, AgentError>

Create a post from RichText (includes detected facets).

Source

pub async fn delete_post(&self, uri: &AtUri) -> Result<(), AgentError>

Delete a post by AT-URI.

Source

pub async fn like( &self, uri: &AtUri, cid: &Cid, created_at: Option<&str>, ) -> Result<Value, AgentError>

Like a post.

If created_at is None, the current time is used.

Source

pub async fn delete_like(&self, like_uri: &AtUri) -> Result<(), AgentError>

Unlike a post by AT-URI of the like record.

Source

pub async fn repost( &self, uri: &AtUri, cid: &Cid, created_at: Option<&str>, ) -> Result<Value, AgentError>

Repost a post.

If created_at is None, the current time is used.

Source

pub async fn delete_repost(&self, repost_uri: &AtUri) -> Result<(), AgentError>

Delete a repost by AT-URI.

Source

pub async fn follow( &self, subject_did: &Did, created_at: Option<&str>, ) -> Result<Value, AgentError>

Follow a user by DID.

If created_at is None, the current time is used.

Source

pub async fn delete_follow(&self, follow_uri: &AtUri) -> Result<(), AgentError>

Unfollow by AT-URI of the follow record.

Source

pub async fn get_profile( &self, actor: &AtIdentifier, ) -> Result<Value, AgentError>

Get a user’s profile.

Source

pub async fn get_timeline( &self, limit: Option<i64>, cursor: Option<&str>, ) -> Result<Value, AgentError>

Get the home timeline.

Source

pub async fn get_post_thread( &self, uri: &AtUri, depth: Option<i64>, ) -> Result<Value, AgentError>

Get a post thread.

Source

pub async fn search_actors( &self, query: &str, limit: Option<i64>, ) -> Result<Value, AgentError>

Search actors.

Source

pub async fn resolve_handle(&self, handle: &Handle) -> Result<Did, AgentError>

Resolve a handle to a DID.

Source

pub async fn list_notifications( &self, limit: Option<i64>, cursor: Option<&str>, ) -> Result<Value, AgentError>

Get notifications.

Source

pub async fn upload_blob( &self, data: Vec<u8>, content_type: &str, ) -> Result<Value, AgentError>

Upload a blob (image, video, etc.).

Source

pub async fn describe_server(&self) -> Result<Value, AgentError>

Describe the server.

Source

pub async fn logout(&self) -> Result<(), AgentError>

Log out of the current session.

Sends a best-effort deleteSession call using the current refresh token (TS matches this — deleteSession requires the refresh JWT, not the access JWT). Clears local session state whether or not the server call succeeds, so the agent always ends up unauthenticated.

Source

pub async fn create_account( &self, handle: &Handle, password: &str, email: Option<&str>, extra: Option<Value>, ) -> Result<Session, AgentError>

Create a new account on the current service.

extra is merged into the request body — useful for passing inviteCode, verificationCode, or custom provider-specific fields without this method’s signature needing to know every option the server supports.

On success, the new session is stored and Create is emitted.

Source

pub async fn upsert_profile<F>(&self, mutate: F) -> Result<Value, AgentError>
where F: Fn(Value) -> Value,

Create-or-update the signed-in user’s app.bsky.actor.profile record.

The mutate closure receives the existing profile record (or serde_json::Value::Null if none exists) and returns the desired next state. This pattern mirrors TS AtpAgent.upsertProfile(updateFn).

The write uses putRecord with swapRecord for CAS safety; if the swap fails we retry up to 5 times with a fresh read.

Auto Trait Implementations§

§

impl Freeze for Agent

§

impl !RefUnwindSafe for Agent

§

impl Send for Agent

§

impl Sync for Agent

§

impl Unpin for Agent

§

impl UnsafeUnpin for Agent

§

impl !UnwindSafe for Agent

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more