use std::path::PathBuf;
use std::sync::Arc;
use std::time::Duration;
use serde_json::Value;
use tokio::sync::oneshot;
use crate::acp::{AcpDriverServices, AcpWorkerDriver};
use crate::configuration::BundleMember;
use crate::tmux::TmuxTransport;
use crate::transports::ui::{UiTransport, UiTransportServices};
pub use crate::configuration::PromptReadinessTemplate;
use crate::envelope::{AddressIdentity, EnvelopeRenderInput, PromptBatchSettings, render_envelope};
use crate::transports::vocabulary::{LookSnapshotPayload, SendOutcome};
pub type OutcomeFuture = oneshot::Receiver<SingleDeliveryOutcome>;
pub trait Transport {
fn startup(&mut self, context: StartupContext) -> Result<TransportStatus, TransportError>;
fn mailw(&mut self, envelope: DeliveryEnvelope) -> OutcomeFuture {
let _ = envelope;
unimplemented!("mailw lands with the per-transport internal delivery task")
}
fn raww(&mut self, content: String, append_enter: bool) -> OutcomeFuture {
let _ = (content, append_enter);
unimplemented!("raww lands with the per-transport internal delivery task")
}
fn is_ready(&self) -> bool;
fn shutdown(&mut self);
fn give_output(&self) -> Option<Arc<dyn OutputView>>;
}
pub trait OutputView: Send + Sync {
fn look(&self, mode: LookMode) -> Result<LookSnapshotPayload, TransportError>;
}
#[allow(clippy::large_enum_variant)]
pub enum TransportImpl {
Acp(Box<AcpWorkerDriver>),
Tmux(TmuxTransport),
Ui(UiTransport),
Pubsub,
Pty,
}
impl TransportImpl {
#[must_use]
pub fn acp(
target_member: BundleMember,
runtime_directory: PathBuf,
namespace: String,
services: AcpDriverServices,
batch_settings: PromptBatchSettings,
) -> Self {
Self::Acp(Box::new(AcpWorkerDriver::new(
target_member,
runtime_directory,
namespace,
services,
batch_settings,
)))
}
#[must_use]
pub fn tmux(batch_settings: PromptBatchSettings) -> Self {
Self::Tmux(TmuxTransport::new(batch_settings))
}
#[must_use]
pub fn ui(services: UiTransportServices) -> Self {
Self::Ui(UiTransport::new(services))
}
#[must_use]
pub fn can_be_looked(&self) -> bool {
match self {
Self::Acp(_) | Self::Tmux(_) | Self::Pty => true,
Self::Ui(_) | Self::Pubsub => false,
}
}
#[must_use]
pub fn can_be_written(&self) -> bool {
match self {
Self::Acp(_) | Self::Tmux(_) | Self::Pty => true,
Self::Ui(_) | Self::Pubsub => false,
}
}
#[must_use]
pub fn can_stream_output(&self) -> bool {
match self {
Self::Acp(_) | Self::Pty => true,
Self::Tmux(_) | Self::Ui(_) | Self::Pubsub => false,
}
}
#[must_use]
pub fn can_give_choices(&self) -> bool {
match self {
Self::Acp(_) => true,
Self::Tmux(_) | Self::Ui(_) | Self::Pubsub | Self::Pty => false,
}
}
pub fn startup(&mut self, context: StartupContext) -> Result<TransportStatus, TransportError> {
match self {
Self::Acp(transport) => transport.startup(context),
Self::Tmux(transport) => transport.startup(context),
Self::Ui(transport) => transport.startup(context),
Self::Pubsub => unimplemented!("Pubsub transport not yet implemented"),
Self::Pty => unimplemented!("PTY transport not yet implemented"),
}
}
pub fn mailw(&mut self, envelope: DeliveryEnvelope) -> OutcomeFuture {
match self {
Self::Acp(transport) => transport.mailw(envelope),
Self::Tmux(transport) => transport.mailw(envelope),
Self::Ui(transport) => transport.mailw(envelope),
Self::Pubsub => unimplemented!("Pubsub transport not yet implemented"),
Self::Pty => unimplemented!("PTY transport not yet implemented"),
}
}
pub fn raww(&mut self, content: String, append_enter: bool) -> OutcomeFuture {
match self {
Self::Acp(transport) => transport.raww(content, append_enter),
Self::Tmux(transport) => transport.raww(content, append_enter),
Self::Ui(transport) => transport.raww(content, append_enter),
Self::Pubsub => unimplemented!("Pubsub transport not yet implemented"),
Self::Pty => unimplemented!("PTY transport not yet implemented"),
}
}
#[must_use]
pub fn is_ready(&self) -> bool {
match self {
Self::Acp(transport) => transport.is_ready(),
Self::Tmux(transport) => transport.is_ready(),
Self::Ui(transport) => transport.is_ready(),
Self::Pubsub => false,
Self::Pty => unimplemented!("PTY transport not yet implemented"),
}
}
pub fn shutdown(&mut self) {
match self {
Self::Acp(transport) => transport.shutdown(),
Self::Tmux(transport) => transport.shutdown(),
Self::Ui(transport) => transport.shutdown(),
Self::Pubsub => {}
Self::Pty => unimplemented!("PTY transport not yet implemented"),
}
}
pub fn give_output(&self) -> Option<Arc<dyn OutputView>> {
match self {
Self::Acp(transport) => transport.give_output(),
Self::Tmux(transport) => transport.give_output(),
Self::Ui(transport) => transport.give_output(),
Self::Pubsub => None,
Self::Pty => unimplemented!("PTY transport not yet implemented"),
}
}
}
pub type Chooser = Arc<dyn Fn(ChoiceToMake) -> ChoiceMade + Send + Sync>;
#[derive(Clone, Debug)]
pub struct ChoiceToMake {
pub request_id: u64,
pub message_id: String,
pub target_session: String,
pub decider_sessions: Vec<String>,
pub title: String,
pub species: String,
pub details: Value,
pub options: Vec<ThingToChoose>,
}
#[derive(Clone, Debug)]
pub struct ThingToChoose {
pub option_id: String,
pub name: String,
pub species: String,
}
#[derive(Clone, Debug)]
pub enum ChoiceMade {
Chosen {
option_id: String,
decided_by: String,
},
Cancelled {
decided_by: String,
reason_code: String,
reason: Option<String>,
},
}
#[derive(Clone)]
pub struct StartupContext {
pub namespace: String,
pub runtime_directory: PathBuf,
pub target_member: BundleMember,
pub choose: Chooser,
}
impl std::fmt::Debug for StartupContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("StartupContext")
.field("namespace", &self.namespace)
.field("runtime_directory", &self.runtime_directory)
.field("target_member", &self.target_member)
.field("choose", &"<Chooser>")
.finish()
}
}
#[derive(Clone, Debug)]
pub struct DeliveryEnvelope {
pub message_id: String,
pub message: DeliveryMessage,
pub append_enter: bool,
pub choice_decider_sessions: Vec<String>,
pub quiet_window: Duration,
pub quiescence_timeout: Option<Duration>,
}
#[derive(Clone, Debug)]
pub struct DeliveryParty {
pub session: String,
pub display_name: Option<String>,
}
#[derive(Clone, Debug)]
pub struct DeliveryMessage {
pub body: String,
pub created_at: String,
pub namespace: String,
pub sender: DeliveryParty,
pub target: DeliveryParty,
pub cc: Vec<DeliveryParty>,
pub authenticated_identity: Option<String>,
}
impl DeliveryParty {
fn to_address(&self) -> AddressIdentity {
AddressIdentity {
session_name: self.session.clone(),
display_name: self.display_name.clone(),
}
}
}
impl DeliveryMessage {
#[must_use]
pub fn render_pane_envelope(&self, message_id: &str) -> String {
render_envelope(&EnvelopeRenderInput {
message_id: message_id.to_string(),
created_at: self.created_at.clone(),
from: self.sender.to_address(),
to: vec![self.target.to_address()],
cc: self.cc.iter().map(DeliveryParty::to_address).collect(),
subject: None,
body: self.body.clone(),
})
}
}
#[derive(Debug)]
pub enum DeliveryWaitError {
Timeout {
timeout: Duration,
readiness_mismatch: bool,
mismatch_reason: Option<String>,
},
Failed {
reason: String,
},
Shutdown,
}
#[derive(Clone, Debug)]
pub struct SingleDeliveryOutcome {
pub target_session: String,
pub message_id: String,
pub outcome: SendOutcome,
pub reason_code: Option<String>,
pub reason: Option<String>,
pub details: Option<Value>,
}
#[derive(Clone, Debug)]
pub struct TransportStatus {
pub readiness: TransportReadiness,
}
#[derive(Clone, Debug)]
pub enum TransportReadiness {
Ready,
Pending,
Unavailable { code: String, reason: String },
}
#[derive(Clone, Debug)]
pub struct TransportError {
pub code: String,
pub reason: String,
pub details: Option<Value>,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct LookMode {
pub lines: Option<u64>,
pub offset: Option<u64>,
pub prime_timeout: Duration,
}