Skip to main content

supercode_frontend_model/
lib.rs

1//! Presentation-framework-neutral state for clients of `supercode.sdk.v1`.
2//!
3//! This crate is a client implementation, not a runtime protocol. It reduces
4//! the canonical frontend descriptor, history, requests, and sequenced events
5//! into transcript/composer state and semantic intents. It deliberately has no
6//! terminal, renderer, donor-client, provider, persistence, scheduler, import,
7//! export, or reduction implementation dependency.
8
9mod capabilities;
10mod composer;
11mod paste_burst;
12mod transcript;
13
14pub use capabilities::ComposerCapabilities;
15pub use composer::{
16    ComposerAction, ComposerInputEvent, ComposerKey, ComposerKeyCode, ComposerModel,
17    ComposerOverlay, ComposerOverlayKind, ComposerTurnState,
18};
19pub use paste_burst::{CharDecision, FlushResult, PasteBurst, RetroGrab};
20pub use transcript::{CellState, TranscriptCell, TranscriptKind, TranscriptModel};
21
22use serde_json::Value;
23
24/// Produce a bounded, control-free semantic description while retaining the
25/// exact opaque value in the client model for inspection and round trips.
26pub fn semantic_value_summary(value: &Value, label: &str, limit: usize) -> String {
27    for field in [
28        "summary", "prompt", "message", "question", "tool", "command", "reason", "detail", "error",
29        "name",
30    ] {
31        if let Some(text) = value.get(field).and_then(Value::as_str) {
32            return bounded_text(text, limit);
33        }
34    }
35
36    let summary = match value {
37        Value::Object(fields) if fields.is_empty() => format!("{label} (no details)"),
38        Value::Object(fields) => {
39            let mut names = fields
40                .keys()
41                .take(6)
42                .map(|name| bounded_metadata(name, 32))
43                .collect::<Vec<_>>();
44            if fields.len() > names.len() {
45                names.push("…".into());
46            }
47            format!("{label} (fields: {})", names.join(", "))
48        }
49        Value::Array(items) => format!("{label} ({} items)", items.len()),
50        Value::String(text) => bounded_text(text, limit),
51        Value::Number(number) => format!("{label}: {number}"),
52        Value::Bool(value) => format!("{label}: {value}"),
53        Value::Null => format!("{label} (no details)"),
54    };
55    bounded_text(&summary, limit)
56}
57
58fn bounded_metadata(input: &str, limit: usize) -> String {
59    let flattened = strip_controls(input)
60        .chars()
61        .map(|character| match character {
62            '\n' | '\t' => ' ',
63            character => character,
64        })
65        .collect::<String>();
66    bounded_text(&flattened, limit)
67}
68
69fn bounded_text(input: &str, limit: usize) -> String {
70    let sanitized = strip_controls(input);
71    if sanitized.chars().count() <= limit {
72        return sanitized;
73    }
74    let mut preview = sanitized
75        .chars()
76        .take(limit.saturating_sub(1))
77        .collect::<String>();
78    preview.push('…');
79    preview
80}
81
82fn strip_controls(input: &str) -> String {
83    #[derive(Clone, Copy)]
84    enum State {
85        Text,
86        Escape,
87        Csi,
88        Osc,
89        OscEscape,
90    }
91
92    let mut state = State::Text;
93    let mut output = String::with_capacity(input.len());
94    for character in input.chars() {
95        state = match state {
96            State::Text if character == '\u{1b}' => State::Escape,
97            State::Text => {
98                if character == '\n' || character == '\t' || !character.is_control() {
99                    output.push(character);
100                }
101                State::Text
102            }
103            State::Escape if character == '[' => State::Csi,
104            State::Escape if character == ']' => State::Osc,
105            State::Escape => State::Text,
106            State::Csi if ('@'..='~').contains(&character) => State::Text,
107            State::Csi => State::Csi,
108            State::Osc if character == '\u{7}' => State::Text,
109            State::Osc if character == '\u{1b}' => State::OscEscape,
110            State::Osc => State::Osc,
111            State::OscEscape if character == '\\' => State::Text,
112            State::OscEscape if character == '\u{1b}' => State::OscEscape,
113            State::OscEscape => State::Osc,
114        };
115    }
116    output
117}