use std::collections::BTreeMap;
use std::sync::Arc;
use async_trait::async_trait;
use serde_json::Value;
use super::{
HarnessEvent, RuntimeAttachRequest, RuntimeBackend, RuntimeCapabilities, RuntimeConnection,
RuntimeEndpoint, RuntimeHandle, RuntimeInput, RuntimeStartRequest,
};
use crate::frontend::HttpFrontendRuntime;
use crate::{
FrontendApprovalDecision, FrontendAttachment, FrontendElicitationAction, FrontendRequestKind,
FrontendResponse, FrontendRuntime, FrontendRuntimeError, HarnessId, ResolvedLiveRuntime,
Result,
};
pub struct SupercodeHttpRuntimeBackend {
receipt: ResolvedLiveRuntime,
}
impl SupercodeHttpRuntimeBackend {
pub fn new(receipt: ResolvedLiveRuntime) -> Self {
Self { receipt }
}
}
#[async_trait]
impl RuntimeBackend for SupercodeHttpRuntimeBackend {
fn harness(&self) -> HarnessId {
HarnessId::new(&self.receipt.source.harness)
}
fn capabilities(&self) -> RuntimeCapabilities {
RuntimeCapabilities {
start_session: false,
resume_session: false,
attach_existing_process: true,
send_input: true,
stream_events: true,
interrupt: true,
steer: true,
respond_to_requests: true,
}
}
async fn start(&self, _request: RuntimeStartRequest) -> Result<Box<dyn RuntimeConnection>> {
Err(crate::Error::Other(
"a Supercode live receipt can only attach to its existing runtime".into(),
))
}
async fn attach(&self, request: RuntimeAttachRequest) -> Result<Box<dyn RuntimeConnection>> {
self.attach_existing(request).await
}
async fn attach_existing(
&self,
request: RuntimeAttachRequest,
) -> Result<Box<dyn RuntimeConnection>> {
if request.runtime_id != self.receipt.source.session_id {
return Err(crate::Error::Other(
"live runtime receipt does not belong to the requested source session".into(),
));
}
if request.cwd.as_ref().is_some_and(|cwd| {
normalized_path(cwd) != normalized_path(&self.receipt.source.workspace)
}) {
return Err(crate::Error::Other(
"live runtime receipt does not belong to the requested workspace".into(),
));
}
let remote =
HttpFrontendRuntime::connect(self.receipt.base_url.clone(), self.receipt.token.clone())
.await
.map_err(frontend_error)?;
let descriptor = FrontendRuntime::describe(remote.as_ref())
.await
.map_err(frontend_error)?;
if descriptor.session_id != self.receipt.runtime_session_id
|| descriptor.source_harness.as_deref() != Some(self.receipt.source.harness.as_str())
{
return Err(crate::Error::Other(
"live runtime identity did not match its trusted receipt".into(),
));
}
let attachment = FrontendRuntime::attach(remote.as_ref(), 10_000)
.await
.map_err(frontend_error)?;
Ok(Box::new(SupercodeHttpRuntimeConnection {
handle: RuntimeHandle {
harness: HarnessId::new(&self.receipt.source.harness),
runtime_id: descriptor.session_id,
endpoint: RuntimeEndpoint::Http {
base_url: self.receipt.endpoint.to_string(),
protocol: "supercode-frontend-http-v1".into(),
},
},
remote,
attachment,
requests: BTreeMap::new(),
}))
}
}
struct SupercodeHttpRuntimeConnection {
handle: RuntimeHandle,
remote: Arc<HttpFrontendRuntime>,
attachment: FrontendAttachment,
requests: BTreeMap<u64, FrontendRequestKind>,
}
#[async_trait]
impl RuntimeConnection for SupercodeHttpRuntimeConnection {
fn handle(&self) -> &RuntimeHandle {
&self.handle
}
async fn send_input(&mut self, input: RuntimeInput) -> Result<Option<String>> {
FrontendRuntime::send_input_with_images(self.remote.clone(), input.text, input.image_urls)
.await
.map_err(frontend_error)?;
Ok(None)
}
async fn next_event(&mut self) -> Result<Option<HarnessEvent>> {
loop {
let event = self.attachment.next_event().await.map_err(frontend_error)?;
if let Some(event) = self.project_live_event(event.sequence, event.kind, event.payload)
{
return Ok(Some(event));
}
}
}
async fn interrupt(&mut self) -> Result<()> {
FrontendRuntime::interrupt(self.remote.as_ref())
.await
.map(|_| ())
.map_err(frontend_error)
}
async fn steer(&mut self, text: String) -> Result<()> {
FrontendRuntime::steer(self.remote.as_ref(), text)
.await
.map_err(frontend_error)
}
async fn respond(&mut self, request_id: Value, response: Value) -> Result<()> {
let descriptor = FrontendRuntime::describe(self.remote.as_ref())
.await
.map_err(frontend_error)?;
if !descriptor.actions.respond {
return Err(crate::Error::Other(
"SDK action `respond` is not supported by this runtime".into(),
));
}
let id = request_id
.as_u64()
.ok_or_else(|| crate::Error::Other("Supercode request id must be an integer".into()))?;
let kind = self
.requests
.remove(&id)
.ok_or_else(|| crate::Error::Other(format!("Supercode request {id} is not pending")))?;
let response = frontend_response(id, kind, response)?;
FrontendRuntime::respond(self.remote.as_ref(), response)
.await
.map_err(frontend_error)
}
async fn close(&mut self) -> Result<()> {
Ok(())
}
}
impl SupercodeHttpRuntimeConnection {
fn project_live_event(
&mut self,
sequence: u64,
kind: String,
payload: Value,
) -> Option<HarnessEvent> {
if kind == "request" {
self.record_request(&payload);
}
Some(HarnessEvent {
sequence: Some(sequence),
kind,
payload,
})
}
fn record_request(&mut self, payload: &Value) {
let Some(request) = payload.get("request") else {
return;
};
let Some(id) = request.get("id").and_then(Value::as_u64) else {
return;
};
let Some(kind) = request
.get("kind")
.cloned()
.and_then(|value| serde_json::from_value::<FrontendRequestKind>(value).ok())
else {
return;
};
self.requests.insert(id, kind);
}
}
fn frontend_response(
request_id: u64,
kind: FrontendRequestKind,
response: Value,
) -> Result<FrontendResponse> {
match kind {
FrontendRequestKind::Approval => {
let decision = match response.get("decision").and_then(Value::as_str) {
Some("allow") => FrontendApprovalDecision::Allow,
Some("allow_for_session") => FrontendApprovalDecision::AllowForSession,
Some("deny") => FrontendApprovalDecision::Deny,
other => {
return Err(crate::Error::Other(format!(
"invalid Supercode approval response: {other:?}"
)))
}
};
Ok(FrontendResponse::Approval {
request_id,
decision,
})
}
FrontendRequestKind::Elicitation | FrontendRequestKind::Other => {
let action = match response.get("action").and_then(Value::as_str) {
Some("accept") => FrontendElicitationAction::Accept,
Some("decline") => FrontendElicitationAction::Decline,
Some("cancel") => FrontendElicitationAction::Cancel,
other => {
return Err(crate::Error::Other(format!(
"invalid Supercode elicitation response: {other:?}"
)))
}
};
let content = response.get("content").cloned();
Ok(if kind == FrontendRequestKind::Elicitation {
FrontendResponse::Elicitation {
request_id,
action,
content,
}
} else {
FrontendResponse::Other {
request_id,
action,
content,
}
})
}
}
}
fn frontend_error(error: FrontendRuntimeError) -> crate::Error {
error.into()
}
fn normalized_path(path: &std::path::Path) -> std::path::PathBuf {
std::fs::canonicalize(path).unwrap_or_else(|_| path.to_path_buf())
}