use std::sync::Arc;
use supercode_harness::frontend::FrontendAttachment;
use supercode_harness::frontend::FrontendEvent;
use supercode_harness::frontend::FrontendOperationInvocation;
use supercode_harness::frontend::FrontendOperationResult;
use supercode_harness::frontend::FrontendResponse;
use supercode_harness::frontend::FrontendRuntime;
use supercode_harness::frontend::FrontendRuntimeDescriptor;
use supercode_harness::frontend::FrontendRuntimeError;
use supercode_harness::ChatMessage;
use crate::composer::ComposerAction;
use crate::composer::ComposerModel;
use crate::transcript::TranscriptModel;
pub struct TerminalRuntimeView {
runtime: Arc<dyn FrontendRuntime>,
attachment: FrontendAttachment,
}
impl TerminalRuntimeView {
pub async fn attach(
runtime: Arc<dyn FrontendRuntime>,
history_limit: usize,
) -> Result<Self, FrontendRuntimeError> {
let attachment = runtime.attach(history_limit).await?;
Ok(Self {
runtime,
attachment,
})
}
pub fn descriptor(&self) -> &FrontendRuntimeDescriptor {
&self.attachment.descriptor
}
pub fn controller(&self) -> Arc<dyn FrontendRuntime> {
self.runtime.clone()
}
pub fn history(&self) -> &[ChatMessage] {
&self.attachment.history
}
pub fn history_cursor(&self) -> u64 {
self.attachment.history_cursor
}
pub fn transcript_model(&self) -> TranscriptModel {
TranscriptModel::from_history(self.history(), self.history_cursor())
}
pub async fn next_event(&mut self) -> Result<FrontendEvent, FrontendRuntimeError> {
self.attachment.next_event().await
}
pub fn next_replay_event(&mut self) -> Option<FrontendEvent> {
self.attachment.next_replay_event()
}
pub async fn update_transcript(
&mut self,
transcript: &mut TranscriptModel,
) -> Result<bool, FrontendRuntimeError> {
let event = self.next_event().await?;
Ok(transcript.apply_event(&event))
}
pub async fn update_ui(
&mut self,
transcript: &mut TranscriptModel,
composer: &mut ComposerModel,
) -> Result<bool, FrontendRuntimeError> {
let event = self.next_event().await?;
let transcript_changed = transcript.apply_event(&event);
let composer_changed = composer.apply_event(&event);
Ok(transcript_changed || composer_changed)
}
pub async fn dispatch_composer_action(
&self,
action: ComposerAction,
composer: &mut ComposerModel,
transcript: &mut TranscriptModel,
) -> Result<(), FrontendRuntimeError> {
dispatch_runtime_action(self.runtime.as_ref(), action, composer, transcript).await
}
pub async fn submit(&self, prompt: impl Into<String>) -> Result<String, FrontendRuntimeError> {
self.runtime.submit(prompt.into()).await
}
pub async fn interrupt(&self) -> Result<bool, FrontendRuntimeError> {
self.runtime.interrupt().await
}
pub async fn steer(&self, prompt: impl Into<String>) -> Result<(), FrontendRuntimeError> {
self.runtime.steer(prompt.into()).await
}
pub async fn respond(&self, response: FrontendResponse) -> Result<(), FrontendRuntimeError> {
self.runtime.respond(response).await
}
pub async fn invoke(
&self,
operation: FrontendOperationInvocation,
) -> Result<FrontendOperationResult, FrontendRuntimeError> {
self.runtime.invoke(operation).await
}
}
async fn dispatch_runtime_action(
runtime: &dyn FrontendRuntime,
action: ComposerAction,
composer: &mut ComposerModel,
transcript: &mut TranscriptModel,
) -> Result<(), FrontendRuntimeError> {
match action {
ComposerAction::Submit(prompt) => {
if let Err(error) = runtime.submit(prompt).await {
if !is_interrupted_submit_error(&error) {
composer.record_dispatch_failure("submit");
}
return Err(error);
}
}
ComposerAction::Invoke(operation) => {
if let Err(error) = runtime.invoke(operation).await {
composer.record_dispatch_failure("invoke");
return Err(error);
}
}
ComposerAction::Steer(prompt) => runtime.steer(prompt).await?,
ComposerAction::Interrupt => {
runtime.interrupt().await?;
}
ComposerAction::Respond {
response,
request,
resolution,
} => {
let request_id = match &response {
FrontendResponse::Approval { request_id, .. }
| FrontendResponse::Elicitation { request_id, .. }
| FrontendResponse::Other { request_id, .. } => *request_id,
};
if let Err(error) = runtime.respond(response).await {
composer.restore_request(request);
return Err(error);
}
transcript.resolve_frontend_request(request_id, &resolution);
}
}
Ok(())
}
pub(crate) fn is_interrupted_submit_error(error: &FrontendRuntimeError) -> bool {
matches!(
error,
FrontendRuntimeError::Submit(supercode_harness::server::RuntimeSubmitError::Interrupted)
)
}