Something went wrong. Try again.
Codex, Claude, and local AI agents on the Commodore 64 Ultimate.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212pub const HEADER_LEN: usize = 4;pub const MAX_PAYLOAD: usize = 92;pub const HELLO_TEXT: &[u8] = b"PROMPT64/2";pub const PROTOCOL_MAJOR: u8 = 2;pub const PROTOCOL_MINOR: u8 = 0;pub const CAPABILITIES: u16 = 0x00ff;pub const HELLO_REPLY: [u8; 4] = [ PROTOCOL_MAJOR, PROTOCOL_MINOR, CAPABILITIES as u8, (CAPABILITIES >> 8) as u8,];
#[derive(Clone, Copy, PartialEq)]#[repr(u8)]pub enum MessageType { Hello = 0x01, HelloOk = 0x02, LeaseGet = 0x03, LeaseState = 0x04, Reconnect = 0x05, ReconnectState = 0x06, ReconnectDone = 0x07, SessionRelease = 0x08, Heartbeat = 0x09, HeartbeatAck = 0x0a, UserText = 0x10, AssistantDelta = 0x11, TurnDone = 0x12, ReasoningDelta = 0x13, TurnCancel = 0x14, TurnCancelled = 0x15, ApprovalRequest = 0x16, ApprovalDecision = 0x17, ActivityEvent = 0x18, TurnFailed = 0x19, HistoryText = 0x1a, ApprovalAck = 0x1b, SettingsGet = 0x20, SettingsSet = 0x21, SettingsState = 0x22, WorkspaceSet = 0x23, WorkspaceState = 0x24, SessionListGet = 0x25, SessionEntry = 0x26, SessionListDone = 0x27, SessionSelect = 0x28, SessionState = 0x29, AccessSet = 0x2a, AccessState = 0x2b, HistoryGet = 0x2c, HistoryDone = 0x2d, DashboardGet = 0x30, DashboardEntry = 0x31, DashboardDone = 0x32, ActionGet = 0x33, ActionText = 0x34, DiffGet = 0x35, DiffLine = 0x36, DiffDone = 0x37, Error = 0x7f,}
impl MessageType { fn from_byte(value: u8) -> Result<Self, ()> { match value { 0x01 => Ok(Self::Hello), 0x02 => Ok(Self::HelloOk), 0x03 => Ok(Self::LeaseGet), 0x04 => Ok(Self::LeaseState), 0x05 => Ok(Self::Reconnect), 0x06 => Ok(Self::ReconnectState), 0x07 => Ok(Self::ReconnectDone), 0x08 => Ok(Self::SessionRelease), 0x09 => Ok(Self::Heartbeat), 0x0a => Ok(Self::HeartbeatAck), 0x10 => Ok(Self::UserText), 0x11 => Ok(Self::AssistantDelta), 0x12 => Ok(Self::TurnDone), 0x13 => Ok(Self::ReasoningDelta), 0x14 => Ok(Self::TurnCancel), 0x15 => Ok(Self::TurnCancelled), 0x16 => Ok(Self::ApprovalRequest), 0x17 => Ok(Self::ApprovalDecision), 0x18 => Ok(Self::ActivityEvent), 0x19 => Ok(Self::TurnFailed), 0x1a => Ok(Self::HistoryText), 0x1b => Ok(Self::ApprovalAck), 0x20 => Ok(Self::SettingsGet), 0x21 => Ok(Self::SettingsSet), 0x22 => Ok(Self::SettingsState), 0x23 => Ok(Self::WorkspaceSet), 0x24 => Ok(Self::WorkspaceState), 0x25 => Ok(Self::SessionListGet), 0x26 => Ok(Self::SessionEntry), 0x27 => Ok(Self::SessionListDone), 0x28 => Ok(Self::SessionSelect), 0x29 => Ok(Self::SessionState), 0x2a => Ok(Self::AccessSet), 0x2b => Ok(Self::AccessState), 0x2c => Ok(Self::HistoryGet), 0x2d => Ok(Self::HistoryDone), 0x30 => Ok(Self::DashboardGet), 0x31 => Ok(Self::DashboardEntry), 0x32 => Ok(Self::DashboardDone), 0x33 => Ok(Self::ActionGet), 0x34 => Ok(Self::ActionText), 0x35 => Ok(Self::DiffGet), 0x36 => Ok(Self::DiffLine), 0x37 => Ok(Self::DiffDone), 0x7f => Ok(Self::Error), _ => Err(()), } }}
pub fn decode_sequence(payload: &[u8]) -> Result<(u16, &[u8]), ()> { if payload.len() < 2 { return Err(()); } Ok((u16::from_le_bytes([payload[0], payload[1]]), &payload[2..]))}
pub const fn sequence_is_newer(sequence: u16, applied: u16) -> bool { let distance = sequence.wrapping_sub(applied); distance != 0 && distance < 0x8000}
#[cfg(test)]mod tests { use super::{decode_sequence, sequence_is_newer};
#[test] fn decodes_and_orders_wrapping_sequences() { assert_eq!( decode_sequence(&[0x34, 0x12, b'X']), Ok((0x1234, b"X".as_slice())) ); assert!(sequence_is_newer(1, 0)); assert!(sequence_is_newer(0, 0xffff)); assert!(!sequence_is_newer(7, 7)); assert!(!sequence_is_newer(6, 7)); }}
pub struct Frame<'a> { pub kind: MessageType, pub flags: u8, pub payload: &'a [u8],}
pub struct FrameBuffer { pub bytes: [u8; HEADER_LEN + MAX_PAYLOAD],}
impl FrameBuffer { pub const fn new() -> Self { Self { bytes: [0; HEADER_LEN + MAX_PAYLOAD], } }
pub fn encode(&mut self, kind: MessageType, payload: &[u8]) -> Result<usize, ()> { if payload.len() > MAX_PAYLOAD { return Err(()); }
let length = payload.len(); self.bytes[0] = kind as u8; self.bytes[1] = 0; self.bytes[2] = length as u8; self.bytes[3] = (length >> 8) as u8; self.bytes[HEADER_LEN..HEADER_LEN + length].copy_from_slice(payload); Ok(HEADER_LEN + length) }
pub fn decode(&self, received: usize) -> Result<Option<Frame<'_>>, ()> { if received < HEADER_LEN { return Ok(None); }
let kind = MessageType::from_byte(self.bytes[0])?; let length = self.bytes[2] as usize | ((self.bytes[3] as usize) << 8); if length > MAX_PAYLOAD { return Err(()); } if received < HEADER_LEN + length { return Ok(None); }
Ok(Some(Frame { kind, flags: self.bytes[1], payload: &self.bytes[HEADER_LEN..HEADER_LEN + length], })) }
pub fn discard(&mut self, received: usize, consumed: usize) -> Result<usize, ()> { if consumed > received || received > self.bytes.len() { return Err(()); }
let remaining = received - consumed; let mut index = 0usize; while index < remaining { self.bytes[index] = self.bytes[index + consumed]; index += 1; } Ok(remaining) }}