supercode-frontend-model 0.4.1

Donor-neutral client state and semantic intents for Supercode frontends.
Documentation
//! Presentation-framework-neutral state for clients of `supercode.sdk.v1`.
//!
//! This crate is a client implementation, not a runtime protocol. It reduces
//! the canonical frontend descriptor, history, requests, and sequenced events
//! into transcript/composer state and semantic intents. It deliberately has no
//! terminal, renderer, donor-client, provider, persistence, scheduler, import,
//! export, or reduction implementation dependency.

mod capabilities;
mod composer;
mod paste_burst;
mod transcript;

pub use capabilities::ComposerCapabilities;
pub use composer::{
    ComposerAction, ComposerInputEvent, ComposerKey, ComposerKeyCode, ComposerModel,
    ComposerOverlay, ComposerOverlayKind, ComposerTurnState,
};
pub use paste_burst::{CharDecision, FlushResult, PasteBurst, RetroGrab};
pub use transcript::{CellState, TranscriptCell, TranscriptKind, TranscriptModel};

use serde_json::Value;

/// Produce a bounded, control-free semantic description while retaining the
/// exact opaque value in the client model for inspection and round trips.
pub fn semantic_value_summary(value: &Value, label: &str, limit: usize) -> String {
    for field in [
        "summary", "prompt", "message", "question", "tool", "command", "reason", "detail", "error",
        "name",
    ] {
        if let Some(text) = value.get(field).and_then(Value::as_str) {
            return bounded_text(text, limit);
        }
    }

    let summary = match value {
        Value::Object(fields) if fields.is_empty() => format!("{label} (no details)"),
        Value::Object(fields) => {
            let mut names = fields
                .keys()
                .take(6)
                .map(|name| bounded_metadata(name, 32))
                .collect::<Vec<_>>();
            if fields.len() > names.len() {
                names.push("".into());
            }
            format!("{label} (fields: {})", names.join(", "))
        }
        Value::Array(items) => format!("{label} ({} items)", items.len()),
        Value::String(text) => bounded_text(text, limit),
        Value::Number(number) => format!("{label}: {number}"),
        Value::Bool(value) => format!("{label}: {value}"),
        Value::Null => format!("{label} (no details)"),
    };
    bounded_text(&summary, limit)
}

fn bounded_metadata(input: &str, limit: usize) -> String {
    let flattened = strip_controls(input)
        .chars()
        .map(|character| match character {
            '\n' | '\t' => ' ',
            character => character,
        })
        .collect::<String>();
    bounded_text(&flattened, limit)
}

fn bounded_text(input: &str, limit: usize) -> String {
    let sanitized = strip_controls(input);
    if sanitized.chars().count() <= limit {
        return sanitized;
    }
    let mut preview = sanitized
        .chars()
        .take(limit.saturating_sub(1))
        .collect::<String>();
    preview.push('');
    preview
}

fn strip_controls(input: &str) -> String {
    #[derive(Clone, Copy)]
    enum State {
        Text,
        Escape,
        Csi,
        Osc,
        OscEscape,
    }

    let mut state = State::Text;
    let mut output = String::with_capacity(input.len());
    for character in input.chars() {
        state = match state {
            State::Text if character == '\u{1b}' => State::Escape,
            State::Text => {
                if character == '\n' || character == '\t' || !character.is_control() {
                    output.push(character);
                }
                State::Text
            }
            State::Escape if character == '[' => State::Csi,
            State::Escape if character == ']' => State::Osc,
            State::Escape => State::Text,
            State::Csi if ('@'..='~').contains(&character) => State::Text,
            State::Csi => State::Csi,
            State::Osc if character == '\u{7}' => State::Text,
            State::Osc if character == '\u{1b}' => State::OscEscape,
            State::Osc => State::Osc,
            State::OscEscape if character == '\\' => State::Text,
            State::OscEscape if character == '\u{1b}' => State::OscEscape,
            State::OscEscape => State::Osc,
        };
    }
    output
}