use crate::llm::actions::{ActionDefinition, Parameter};
use crate::protocol::log_template::LogTemplate;
use serde_json::Value as JsonValue;
#[derive(Clone, Debug)]
pub struct EventType {
pub id: String,
pub description: String,
pub actions: Vec<ActionDefinition>,
no_actions_intentional: bool,
pub parameters: Vec<Parameter>,
pub response_example: JsonValue,
pub alternative_examples: Vec<JsonValue>,
pub log_template: Option<LogTemplate>,
pub on_every_connection: bool,
}
pub fn connect_event_data() -> JsonValue {
serde_json::json!({
"bytes_received": 0,
"answer_with": "If the instruction says to greet, show a banner, prompt or ask for \
something as soon as someone connects, send that text to the client \
now with this protocol's send action (show_message only reaches the \
operator's screen, never the client). Otherwise answer with no actions \
({\"actions\": []}): the client has sent 0 bytes, so there is nothing \
to echo, transform or answer yet.",
})
}
pub fn connect_event_parameters() -> Vec<Parameter> {
vec![
Parameter {
name: "bytes_received".to_string(),
type_hint: "number".to_string(),
description: "How much the client has sent: always 0 on a connect event".to_string(),
required: true,
},
Parameter {
name: "answer_with".to_string(),
type_hint: "string".to_string(),
description: "Which answer a connect event calls for".to_string(),
required: true,
},
]
}
impl EventType {
pub fn new(
id: impl Into<String>,
description: impl Into<String>,
response_example: JsonValue,
) -> Self {
Self {
id: id.into(),
description: description.into(),
actions: Vec::new(),
no_actions_intentional: false,
parameters: Vec::new(),
response_example,
alternative_examples: Vec::new(),
log_template: None,
on_every_connection: false,
}
}
pub fn raised_on_every_connection(mut self) -> Self {
self.on_every_connection = true;
self
}
pub fn with_action(mut self, action: ActionDefinition) -> Self {
self.actions.push(action);
self
}
pub fn with_actions(mut self, actions: Vec<ActionDefinition>) -> Self {
self.actions.extend(actions);
self
}
pub fn with_no_actions(mut self) -> Self {
self.no_actions_intentional = true;
self
}
pub fn effective_response_example(&self) -> JsonValue {
if !Self::is_placeholder(&self.response_example) {
return self.response_example.clone();
}
if let Some(action) = self.actions.first() {
if !Self::is_placeholder(&action.example) {
return action.example.clone();
}
}
serde_json::json!({
"type": "show_message",
"message": format!("Handled {}", self.id),
})
}
pub fn is_placeholder(example: &JsonValue) -> bool {
example.get("type").and_then(|t| t.as_str()) == Some("placeholder")
}
pub fn has_no_usable_actions(&self) -> bool {
self.actions.is_empty() && !self.no_actions_intentional
}
pub fn with_parameter(mut self, parameter: Parameter) -> Self {
self.parameters.push(parameter);
self
}
pub fn with_parameters(mut self, parameters: Vec<Parameter>) -> Self {
self.parameters.extend(parameters);
self
}
pub fn with_alternative_example(mut self, example: JsonValue) -> Self {
self.alternative_examples.push(example);
self
}
pub fn with_log_template(mut self, template: LogTemplate) -> Self {
self.log_template = Some(template);
self
}
pub fn action_names(&self) -> Vec<String> {
self.actions.iter().map(|a| a.name.clone()).collect()
}
pub fn to_prompt_description(&self) -> String {
let mut result = String::new();
result.push_str(&format!("Event Type: {}\n", self.id));
result.push_str(&format!("Description: {}\n\n", self.description));
if !self.parameters.is_empty() {
result.push_str("Event Input Data:\n");
for param in &self.parameters {
result.push_str(&format!(
" - {} ({}){}: {}\n",
param.name,
param.type_hint,
if param.required {
", required"
} else {
", optional"
},
param.description
));
}
result.push('\n');
}
if !self.actions.is_empty() {
result.push_str("Available actions for this event:\n\n");
for (i, action) in self.actions.iter().enumerate() {
result.push_str(&format!("{}. {}\n\n", i + 1, action.to_prompt_text()));
}
} else {
result.push_str("No specific actions available for this event.\n");
}
result
}
}
#[derive(Clone, Debug)]
pub struct Event {
pub event_type: &'static EventType,
pub data: JsonValue,
}
impl Event {
pub fn new(event_type: &'static EventType, data: JsonValue) -> Self {
Self { event_type, data }
}
pub fn id(&self) -> &str {
&self.event_type.id
}
pub fn to_prompt_description(&self) -> String {
self.event_type.to_prompt_description()
}
}
pub fn format_event_types_for_prompt(event_types: &[EventType]) -> String {
if event_types.is_empty() {
return String::new();
}
let mut result = String::from("\nEVENT TYPES:\n");
result.push_str("This protocol can emit the following event types:\n\n");
for event_type in event_types {
result.push_str(&format!(
"• {} - {}\n",
event_type.id, event_type.description
));
result.push_str(&format!(
" Available actions: {}\n",
event_type.action_names().join(", ")
));
}
result.push('\n');
result
}
pub fn format_script_template_for_prompt(event_types: &[EventType]) -> String {
if event_types.is_empty() {
return String::new();
}
let event_ids: Vec<String> = event_types
.iter()
.map(|e| format!("\"{}\"", e.id))
.collect();
format!(
r#"
SCRIPT TEMPLATE for this protocol:
When creating a script, structure it with a switch/case on the event type:
Python example:
import json, sys
data = json.load(sys.stdin)
event_type = data['event_type_id']
if event_type == "event_id_1":
# Handle this event type
result = {{"actions": [{{"type": "action_name", "param": value}}]}}
elif event_type == "event_id_2":
# Handle another event type
result = {{"actions": [{{"type": "other_action", "param": value}}]}}
else:
# Unknown event - fallback to LLM
result = {{"fallback_to_llm": true, "fallback_reason": "Unknown event type"}}
print(json.dumps(result))
JavaScript example:
const data = JSON.parse(require('fs').readFileSync(0, 'utf-8'));
const eventType = data.event_type_id;
let result;
switch (eventType) {{
case "event_id_1":
result = {{"actions": [{{"type": "action_name", "param": value}}]}};
break;
case "event_id_2":
result = {{"actions": [{{"type": "other_action", "param": value}}]}};
break;
default:
result = {{"fallback_to_llm": true, "fallback_reason": "Unknown event type"}};
}}
console.log(JSON.stringify(result));
Event types for this protocol: {}
"#,
event_ids.join(", ")
)
}