api+remote: ambient sensorium, cancel/interject verbs, SSE event mirror
SendMessageRequest gains ambient (surface sensorium note). RemoteBackend gains spawn_cancel_watch + spawn_interject_pump so TUI remote mode has the same interrupt/type-while-busy semantics as local. StreamEvent is now the full wire mirror of BackendEvent — a new engine event is a compile error at the SSE seam, not a silent skip.
This commit is contained in:
parent
904fbd02d7
commit
165d814492
8 changed files with 439 additions and 79 deletions
|
|
@ -214,9 +214,26 @@ pub struct SendMessageRequest {
|
|||
pub messages: Vec<Message>,
|
||||
#[serde(default)]
|
||||
pub stream: bool,
|
||||
/// Ambient context from the sending surface — what the environment
|
||||
/// senses at the moment of speaking: active window, open apps, cursor
|
||||
/// position, device sensors. Injected as a system note before the user
|
||||
/// message so the agent perceives the room she is being spoken to in.
|
||||
#[serde(default)]
|
||||
pub ambient: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct InterjectRequest {
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
/// The full wire mirror of [`crate::backend::BackendEvent`].
|
||||
///
|
||||
/// Every engine event crosses the SSE boundary — no silent skips. The
|
||||
/// exhaustive `From` impls in both directions mean a new `BackendEvent`
|
||||
/// variant is a compile error here, not an invisible hole in every
|
||||
/// non-TUI surface. Tag values are the wire contract; never rename.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "message_type")]
|
||||
pub enum StreamEvent {
|
||||
#[serde(rename = "assistant_message")]
|
||||
|
|
@ -224,7 +241,11 @@ pub enum StreamEvent {
|
|||
#[serde(rename = "reasoning_message")]
|
||||
ReasoningMessage { content: String },
|
||||
#[serde(rename = "tool_call_message")]
|
||||
ToolCallMessage { tool_call: ToolCall },
|
||||
ToolCallMessage {
|
||||
tool_call: ToolCall,
|
||||
#[serde(default)]
|
||||
round: u32,
|
||||
},
|
||||
#[serde(rename = "tool_return_message")]
|
||||
ToolReturnMessage { tool_return: ToolReturn },
|
||||
#[serde(rename = "souveraine_surfacing")]
|
||||
|
|
@ -233,6 +254,38 @@ pub enum StreamEvent {
|
|||
Reflection { content: String },
|
||||
#[serde(rename = "souveraine_archivist")]
|
||||
Archivist { synthesis: String, pressure: f32 },
|
||||
#[serde(rename = "compaction_warning")]
|
||||
CompactionWarning { pressure: f32, tier: u8 },
|
||||
#[serde(rename = "context_pressure")]
|
||||
ContextPressure { pressure: f32, tokens: usize },
|
||||
#[serde(rename = "inference_strain")]
|
||||
InferenceStrain { attempt: u32, status: u16, model: String },
|
||||
#[serde(rename = "schedule_active")]
|
||||
ScheduleActive { name: String },
|
||||
#[serde(rename = "schedule_complete")]
|
||||
ScheduleComplete { name: String, silent: bool },
|
||||
#[serde(rename = "subconscious_token")]
|
||||
SubconsciousToken { content: String },
|
||||
#[serde(rename = "subconscious_tool_call")]
|
||||
SubconsciousToolCall { name: String, arguments: String },
|
||||
#[serde(rename = "subconscious_tool_result")]
|
||||
SubconsciousToolResult { name: String, output: String, is_error: bool },
|
||||
#[serde(rename = "subconscious_halt")]
|
||||
SubconsciousHalt { reason: String, severity: String },
|
||||
#[serde(rename = "subconscious_pass")]
|
||||
SubconsciousPass { active: bool },
|
||||
#[serde(rename = "atmosphere")]
|
||||
Atmosphere { preset: String },
|
||||
#[serde(rename = "itinerary")]
|
||||
Itinerary { route: String },
|
||||
#[serde(rename = "outfit")]
|
||||
Outfit { name: String },
|
||||
#[serde(rename = "interstitial")]
|
||||
Interstitial { text: String, register: crate::backend::Register },
|
||||
#[serde(rename = "primary_complete")]
|
||||
PrimaryComplete,
|
||||
#[serde(rename = "done")]
|
||||
Done,
|
||||
#[serde(rename = "ping")]
|
||||
Ping,
|
||||
}
|
||||
|
|
@ -247,15 +300,125 @@ impl StreamEvent {
|
|||
StreamEvent::Surfacing { .. } => "souveraine_surfacing",
|
||||
StreamEvent::Reflection { .. } => "souveraine_reflection",
|
||||
StreamEvent::Archivist { .. } => "souveraine_archivist",
|
||||
StreamEvent::CompactionWarning { .. } => "compaction_warning",
|
||||
StreamEvent::ContextPressure { .. } => "context_pressure",
|
||||
StreamEvent::InferenceStrain { .. } => "inference_strain",
|
||||
StreamEvent::ScheduleActive { .. } => "schedule_active",
|
||||
StreamEvent::ScheduleComplete { .. } => "schedule_complete",
|
||||
StreamEvent::SubconsciousToken { .. } => "subconscious_token",
|
||||
StreamEvent::SubconsciousToolCall { .. } => "subconscious_tool_call",
|
||||
StreamEvent::SubconsciousToolResult { .. } => "subconscious_tool_result",
|
||||
StreamEvent::SubconsciousHalt { .. } => "subconscious_halt",
|
||||
StreamEvent::SubconsciousPass { .. } => "subconscious_pass",
|
||||
StreamEvent::Atmosphere { .. } => "atmosphere",
|
||||
StreamEvent::Itinerary { .. } => "itinerary",
|
||||
StreamEvent::Outfit { .. } => "outfit",
|
||||
StreamEvent::Interstitial { .. } => "interstitial",
|
||||
StreamEvent::PrimaryComplete => "primary_complete",
|
||||
StreamEvent::Done => "done",
|
||||
StreamEvent::Ping => "ping",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
impl From<crate::backend::BackendEvent> for StreamEvent {
|
||||
fn from(be: crate::backend::BackendEvent) -> Self {
|
||||
use crate::backend::BackendEvent as BE;
|
||||
match be {
|
||||
BE::Token(content) => Self::AssistantMessage { content },
|
||||
BE::Reasoning(content) => Self::ReasoningMessage { content },
|
||||
BE::Surfacing { source, content, priority } => Self::Surfacing { source, content, priority },
|
||||
BE::Reflection(content) => Self::Reflection { content },
|
||||
BE::Archivist { synthesis, pressure } => Self::Archivist { synthesis, pressure },
|
||||
BE::CompactionWarning { pressure, tier } => Self::CompactionWarning { pressure, tier },
|
||||
BE::ContextPressure(pressure, tokens) => Self::ContextPressure { pressure, tokens },
|
||||
BE::InferenceStrain { attempt, status, model } => Self::InferenceStrain { attempt, status, model },
|
||||
BE::ScheduleActive { name } => Self::ScheduleActive { name },
|
||||
BE::ScheduleComplete { name, silent } => Self::ScheduleComplete { name, silent },
|
||||
BE::ToolCall { id, name, arguments, round } => Self::ToolCallMessage {
|
||||
tool_call: ToolCall { id, function: ToolFunction { name, arguments } },
|
||||
round,
|
||||
},
|
||||
BE::ToolResult { id, name, output, is_error } => Self::ToolReturnMessage {
|
||||
tool_return: ToolReturn {
|
||||
status: if is_error { "error".into() } else { "success".into() },
|
||||
output,
|
||||
id,
|
||||
name,
|
||||
},
|
||||
},
|
||||
BE::SubconsciousToken(content) => Self::SubconsciousToken { content },
|
||||
BE::SubconsciousToolCall { name, arguments } => Self::SubconsciousToolCall { name, arguments },
|
||||
BE::SubconsciousToolResult { name, output, is_error } => {
|
||||
Self::SubconsciousToolResult { name, output, is_error }
|
||||
}
|
||||
BE::SubconsciousHalt { reason, severity } => Self::SubconsciousHalt { reason, severity },
|
||||
BE::SubconsciousPass(active) => Self::SubconsciousPass { active },
|
||||
BE::Atmosphere(preset) => Self::Atmosphere { preset },
|
||||
BE::Itinerary(route) => Self::Itinerary { route },
|
||||
BE::Outfit(name) => Self::Outfit { name },
|
||||
BE::Interstitial { text, register } => Self::Interstitial { text, register },
|
||||
BE::Keepalive => Self::Ping,
|
||||
BE::PrimaryComplete => Self::PrimaryComplete,
|
||||
BE::Done => Self::Done,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StreamEvent> for crate::backend::BackendEvent {
|
||||
fn from(se: StreamEvent) -> Self {
|
||||
use crate::backend::BackendEvent as BE;
|
||||
match se {
|
||||
StreamEvent::AssistantMessage { content } => BE::Token(content),
|
||||
StreamEvent::ReasoningMessage { content } => BE::Reasoning(content),
|
||||
StreamEvent::Surfacing { source, content, priority } => BE::Surfacing { source, content, priority },
|
||||
StreamEvent::Reflection { content } => BE::Reflection(content),
|
||||
StreamEvent::Archivist { synthesis, pressure } => BE::Archivist { synthesis, pressure },
|
||||
StreamEvent::CompactionWarning { pressure, tier } => BE::CompactionWarning { pressure, tier },
|
||||
StreamEvent::ContextPressure { pressure, tokens } => BE::ContextPressure(pressure, tokens),
|
||||
StreamEvent::InferenceStrain { attempt, status, model } => BE::InferenceStrain { attempt, status, model },
|
||||
StreamEvent::ScheduleActive { name } => BE::ScheduleActive { name },
|
||||
StreamEvent::ScheduleComplete { name, silent } => BE::ScheduleComplete { name, silent },
|
||||
StreamEvent::ToolCallMessage { tool_call, round } => BE::ToolCall {
|
||||
id: tool_call.id,
|
||||
name: tool_call.function.name,
|
||||
arguments: tool_call.function.arguments,
|
||||
round,
|
||||
},
|
||||
StreamEvent::ToolReturnMessage { tool_return } => BE::ToolResult {
|
||||
id: tool_return.id,
|
||||
name: tool_return.name,
|
||||
is_error: tool_return.status == "error",
|
||||
output: tool_return.output,
|
||||
},
|
||||
StreamEvent::SubconsciousToken { content } => BE::SubconsciousToken(content),
|
||||
StreamEvent::SubconsciousToolCall { name, arguments } => BE::SubconsciousToolCall { name, arguments },
|
||||
StreamEvent::SubconsciousToolResult { name, output, is_error } => {
|
||||
BE::SubconsciousToolResult { name, output, is_error }
|
||||
}
|
||||
StreamEvent::SubconsciousHalt { reason, severity } => BE::SubconsciousHalt { reason, severity },
|
||||
StreamEvent::SubconsciousPass { active } => BE::SubconsciousPass(active),
|
||||
StreamEvent::Atmosphere { preset } => BE::Atmosphere(preset),
|
||||
StreamEvent::Itinerary { route } => BE::Itinerary(route),
|
||||
StreamEvent::Outfit { name } => BE::Outfit(name),
|
||||
StreamEvent::Interstitial { text, register } => BE::Interstitial { text, register },
|
||||
StreamEvent::Ping => BE::Keepalive,
|
||||
StreamEvent::PrimaryComplete => BE::PrimaryComplete,
|
||||
StreamEvent::Done => BE::Done,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ToolReturn {
|
||||
pub status: String,
|
||||
pub output: String,
|
||||
/// Tool-call id this return answers. Empty on frames from pre-widening servers.
|
||||
#[serde(default)]
|
||||
pub id: String,
|
||||
/// Tool name. Empty on frames from pre-widening servers.
|
||||
#[serde(default)]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
|
|
|
|||
Loading…
Reference in a new issue