blob: 90b2158ceb283f9560d34ba2262bd0cabd4cdd7f [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
//! Native terminal presentation. Controller values correlate UI work; only the
//! independently bound Host service can authorize or perform process effects.
pub mod state;
use crate::appearance::{Color, TextAppearance};
use crate::{ComponentCall, ComponentOutput, Emission, Frame};
use input::InputAction;
pub use maka_agent_facts::shell_result::ShellOutput;
pub use maka_agent_facts::terminal::TerminalSize;
pub use maka_agent_facts::terminal::input;
use maka_plugin_core::contract::{Contract, Interface, Transport};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use uuid::Uuid;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct TerminalViewProps {
/// Changed when the selected canonical resource changes, including read-only views.
#[schemars(length(min = 1))]
pub source: String,
#[schemars(length(min = 1))]
pub accessibility_label: String,
pub output: Option<ShellOutput>,
pub controller: Option<Uuid>,
pub acknowledgement: Option<TerminalInputAcknowledgement>,
pub appearance: TextAppearance,
pub background: Color,
pub caret: Color,
#[schemars(range(min = 1))]
pub rows: u16,
#[schemars(range(min = -1, max = 32767))]
pub tab_index: i16,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct TerminalInputAcknowledgement {
pub owner: Uuid,
pub controller: Uuid,
#[schemars(range(min = 1, max = 9007199254740991u64))]
pub sequence: u64,
/// True only after the exact original mutation has a successful result.
pub accepted: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct TerminalInputBatch {
pub controller: Uuid,
#[schemars(range(min = 1, max = 9007199254740991u64))]
pub sequence: u64,
#[schemars(length(max = 64))]
pub actions: Vec<InputAction>,
pub size: Option<TerminalSize>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum TerminalViewError {
InputFull,
InputFailed,
BudgetExhausted,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct TerminalViewObservation {
pub owner: Uuid,
pub revision: u64,
pub source: String,
pub controller: Option<Uuid>,
pub captured: bool,
pub batch: Option<TerminalInputBatch>,
pub error: Option<TerminalViewError>,
}
pub fn observation_schema() -> serde_json::Value {
schemars::schema_for!(TerminalViewObservation).into()
}
pub fn terminal_view_contract() -> Contract {
Contract {
name: "maka.ui.TerminalView".into(),
version: 1,
transport: Transport::Local,
interface: Interface::Component {
props: schemars::schema_for!(TerminalViewProps).into(),
events: BTreeMap::from([("observed".into(), observation_schema().into())]),
captures: BTreeMap::new(),
},
}
}
pub fn reduce(call: ComponentCall<'_>) -> crate::Result<ComponentOutput<TerminalViewProps>> {
let (revision, props, events) = match call {
ComponentCall::Mount { props } => (0, props, vec![]),
ComponentCall::Update { state, props } => {
(state["revision"].as_u64().unwrap() + 1, props, vec![])
}
ComponentCall::Event {
state,
props,
key: "observe",
input,
..
} => (
state["revision"].as_u64().unwrap() + 1,
props,
vec![Emission {
name: "observed".into(),
value: input.clone(),
}],
),
ComponentCall::Event { .. } => {
return Err(crate::Error::Invalid("unknown terminal input".into()));
}
};
let props: TerminalViewProps =
serde_json::from_value(props.clone()).map_err(|e| crate::Error::Invalid(e.to_string()))?;
if serde_json::to_vec(&props.output)
.map_err(|e| crate::Error::Invalid(e.to_string()))?
.len()
> 52 * 1024
{
return Err(crate::Error::Invalid(
"terminal canonical output exceeds presentation limit".into(),
));
}
Ok(Frame {
revision,
state: serde_json::json!({"revision":revision}),
surface: None,
view: props,
}
.output(events))
}
pub const IMPLEMENTATION: &str = "maka.ui.terminal";
pub fn native() -> maka_plugin_core::package::NativeDefinition {
maka_plugin_core::package::NativeDefinition::new(
IMPLEMENTATION,
serde_json::json!({"type":"object","properties":{},"required":[],"additionalProperties":false}),
[("TerminalView", terminal_view_contract())],
)
}