supercode-harness 0.4.13

The optional native Supercode agent and tool harness
Documentation
//! Runtime adapter for a trusted, already-hosted Supercode SDK runtime.

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,
};

/// Adapter that joins one authenticated Supercode SDK runtime without taking
/// ownership of the process or creating a second model loop.
pub struct SupercodeHttpRuntimeBackend {
    receipt: ResolvedLiveRuntime,
}

impl SupercodeHttpRuntimeBackend {
    /// Build an adapter from a trusted local receipt resolved by the host.
    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<()> {
        // Dropping this HTTP/SSE attachment is a detach. The terminal-owned
        // SDK runtime keeps running until its own frontend exits or closes it.
        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())
}