pub use termesh_core::agent::{AgentEvent, AgentRequest, PermissionDecision, StopReason};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AgentIntegration {
TerminalCli,
Acp,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClientCapabilities {
pub read_text_file: bool,
pub write_text_file: bool,
pub terminal: bool,
}
impl Default for ClientCapabilities {
fn default() -> Self {
Self { read_text_file: true, write_text_file: true, terminal: false }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProposalDecision {
AcceptAll,
AcceptHunk(usize),
RejectHunk(usize),
RejectAll,
}
pub fn permission_for_review(accepted: usize, total: usize) -> PermissionDecision {
if total > 0 && accepted == total {
PermissionDecision::AllowOnce
} else {
PermissionDecision::RejectOnce
}
}
pub trait AgentService: Send {
fn integration(&self) -> AgentIntegration;
fn capabilities(&self) -> ClientCapabilities {
ClientCapabilities::default()
}
fn send(&mut self, request: AgentRequest);
fn poll(&mut self) -> Vec<AgentEvent>;
}
#[derive(Debug, Default)]
pub struct NullAgent;
impl AgentService for NullAgent {
fn integration(&self) -> AgentIntegration {
AgentIntegration::TerminalCli
}
fn send(&mut self, _request: AgentRequest) {}
fn poll(&mut self) -> Vec<AgentEvent> {
Vec::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_default_tier_needs_no_configured_agent() {
assert_eq!(NullAgent.integration(), AgentIntegration::TerminalCli);
assert!(NullAgent.poll().is_empty());
}
#[test]
fn we_advertise_both_file_capabilities() {
let caps = ClientCapabilities::default();
assert!(caps.read_text_file, "serving live buffers is the whole point");
assert!(caps.write_text_file, "saying no just pushes the agent to shell out");
assert!(!caps.terminal, "terminal support is enabled only after runtime wiring");
}
#[test]
fn a_partial_accept_is_reported_as_a_rejection() {
assert_eq!(permission_for_review(3, 3), PermissionDecision::AllowOnce);
assert_eq!(
permission_for_review(2, 3),
PermissionDecision::RejectOnce,
"the agent must not think the file matches what it proposed"
);
assert_eq!(permission_for_review(0, 3), PermissionDecision::RejectOnce);
}
#[test]
fn an_empty_proposal_is_not_an_approval() {
assert_eq!(permission_for_review(0, 0), PermissionDecision::RejectOnce);
}
}