use std::sync::{Arc, Mutex};
use async_trait::async_trait;
use supercode::{
Agent, ChatMessage, ChatRequest, Config, FrontendElicitationAction, FrontendResponse,
FrontendRuntimeError, Provider, RpcEngine, RuntimeSubmitError, SdkRuntime, Session,
SessionFormat, Usage,
};
use supercode_frontend_tui::runtime::TerminalRuntimeView;
use supercode_frontend_tui::terminal::palette::{ColorCapabilities, ColorLevel};
struct BlockingProvider {
entered: tokio::sync::Notify,
release: tokio::sync::Notify,
}
struct SharedProvider(Arc<BlockingProvider>);
#[async_trait]
impl Provider for SharedProvider {
async fn complete(
&self,
_request: &ChatRequest,
on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
) -> supercode::Result<(ChatMessage, Usage)> {
self.0.entered.notify_one();
self.0.release.notified().await;
on_delta("same reply");
Ok((ChatMessage::assistant("same reply"), Usage::default()))
}
}
#[tokio::test]
async fn terminal_skin_preserves_identity_events_busy_errors_and_persistence() {
let style = ColorCapabilities::resolve(false, Some("TERMINAL_STYLE_SENTINEL"), None, false);
assert_eq!(style.level, ColorLevel::Monochrome);
let provider = Arc::new(BlockingProvider {
entered: tokio::sync::Notify::new(),
release: tokio::sync::Notify::new(),
});
let persisted = Arc::new(Mutex::new(String::new()));
let hook = persisted.clone();
let runtime = RpcEngine::new_named(
Agent::with_provider(
Config::builder().system_prompt("sdk conformance").build(),
Box::new(SharedProvider(provider.clone())),
),
"stable-sdk-session",
Some(Box::new(move |agent: &supercode::SdkAgent| {
*hook.lock().unwrap() = serde_json::to_string(agent.history()).unwrap();
})),
);
let view = Arc::new(
TerminalRuntimeView::attach(runtime.clone(), 50)
.await
.unwrap(),
);
let mut events = TerminalRuntimeView::attach(runtime.clone(), 50)
.await
.unwrap();
assert_eq!(view.descriptor().session_id, "stable-sdk-session");
let active = {
let view = view.clone();
tokio::spawn(async move { view.submit("same prompt").await })
};
provider.entered.notified().await;
assert_eq!(
view.submit("busy prompt").await.unwrap_err(),
FrontendRuntimeError::Submit(RuntimeSubmitError::Busy)
);
assert_eq!(
view.respond(FrontendResponse::Other {
request_id: 99,
action: FrontendElicitationAction::Cancel,
content: None,
})
.await
.unwrap_err(),
FrontendRuntimeError::UnsupportedAction("respond")
);
provider.release.notify_waiters();
assert_eq!(active.await.unwrap().unwrap(), "same reply");
let mut observed = Vec::new();
loop {
let event = events.next_event().await.unwrap();
let terminal = event.kind == "turn_succeeded";
observed.push((event.sequence, event.kind));
if terminal {
break;
}
}
assert_eq!(
observed,
vec![
(1, "user_message".into()),
(2, "turn_started".into()),
(3, "text_delta".into()),
(4, "usage".into()),
(5, "turn_completed".into()),
(6, "turn_succeeded".into()),
]
);
let expected = serde_json::to_string(&vec![
ChatMessage::system("sdk conformance"),
ChatMessage::user("same prompt"),
ChatMessage::assistant("same reply"),
])
.unwrap();
assert_eq!(*persisted.lock().unwrap(), expected);
let content = Session::from_native_messages(runtime.attach(50).await.unwrap().history)
.to_jsonl(SessionFormat::Pi)
.unwrap();
let export_path = std::env::temp_dir().join(format!(
"supercode-terminal-export-{}.jsonl",
std::process::id()
));
std::fs::write(&export_path, &content).unwrap();
let reloaded = Session::load(&export_path).unwrap();
assert!(!content.contains("TERMINAL_STYLE_SENTINEL"));
assert!(!reloaded
.to_jsonl(SessionFormat::Pi)
.unwrap()
.contains("TERMINAL_STYLE_SENTINEL"));
std::fs::remove_file(export_path).ok();
runtime.shutdown().await;
}