1pub mod types;
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use std::{convert::TryFrom, fmt};
8
9use crate::types::{AgentEvent, AgentEventError, JsonRpcNotification, ToolCall, ToolResult};
10
11pub const EVENTS_PROTOCOL_VERSION: &str = "0.1.0";
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[non_exhaustive]
16pub enum SessionInitType {
17 Start,
19 Resume,
21}
22
23impl fmt::Display for SessionInitType {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 match self {
26 Self::Start => write!(f, "start"),
27 Self::Resume => write!(f, "resume"),
28 }
29 }
30}
31
32impl TryFrom<&str> for SessionInitType {
33 type Error = AgentEventError;
34
35 fn try_from(value: &str) -> Result<Self, Self::Error> {
36 match value.to_lowercase().as_str() {
37 "start" => Ok(Self::Start),
38 "resume" => Ok(Self::Resume),
39 _ => Err(AgentEventError::InvalidFieldType(format!(
40 "No init type with message: {}",
41 value
42 ))),
43 }
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49#[non_exhaustive]
50pub enum DeltaType {
51 Text,
53 Thinking,
55}
56
57impl fmt::Display for DeltaType {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 match self {
60 Self::Text => write!(f, "text"),
61 Self::Thinking => write!(f, "thinking"),
62 }
63 }
64}
65
66impl From<DeltaType> for Value {
67 fn from(value: DeltaType) -> Self {
68 match value {
69 DeltaType::Text => Value::from("text"),
70 DeltaType::Thinking => Value::from("thinking"),
71 }
72 }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, Copy, Default)]
76pub struct Usage {
77 pub latency: i64,
78 pub input_chars: usize,
79 pub estimated_input_tokens: usize,
80 pub output_chars: usize,
81 pub estimated_output_tokens: usize,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct SessionInitEvent {
87 pub session_id: String,
88 pub model: String,
89 pub provider: String,
90 pub system: String,
91 pub init_type: SessionInitType,
93 pub timestamp: DateTime<Utc>,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct SessionStopEvent {
99 pub session_id: String,
100 pub success: bool,
101 pub result: Option<String>,
102 pub error: Option<String>,
103 pub timestamp: DateTime<Utc>,
104 pub usage: Usage,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct UserPromptSubmitEvent {
110 pub session_id: String,
111 pub turn_id: String,
112 pub prompt: String,
113 pub timestamp: DateTime<Utc>,
114}
115
116#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct StreamDeltaEvent {
119 pub session_id: String,
120 pub turn_id: String,
121 pub delta: String,
122 pub delta_type: DeltaType,
123 pub timestamp: DateTime<Utc>,
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct ToolCallEvent {
129 pub session_id: String,
130 pub turn_id: String,
131 pub name: String,
132 pub input: Value,
133 pub timestamp: DateTime<Utc>,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
138pub struct ToolResultEvent {
139 pub session_id: String,
140 pub turn_id: String,
141 pub result: ToolResult,
143 pub tool_call_id: String,
144 pub timestamp: DateTime<Utc>,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct SkillLoadEvent {
150 pub session_id: String,
151 pub turn_id: String,
152 pub skill_name: String,
153 pub timestamp: DateTime<Utc>,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct AssistantResponseEvent {
159 pub session_id: String,
160 pub turn_id: String,
161 pub full_text: String,
162 pub tool_calls: Option<Vec<ToolCall>>,
163 pub timestamp: DateTime<Utc>,
164}
165
166impl AgentEvent for SessionInitEvent {
167 fn to_jsonrpc(&self) -> JsonRpcNotification {
168 JsonRpcNotification::builder()
169 .method("session.init".into())
170 .add_param("session_id".into(), Value::from(self.session_id.clone()))
171 .add_param("system".into(), Value::from(self.system.clone()))
172 .add_param("model".into(), Value::from(self.model.clone()))
173 .add_param("provider".into(), Value::from(self.provider.clone()))
174 .add_param(
175 "init_type".into(),
176 serde_json::to_value(self.init_type.clone()).unwrap(),
177 )
178 .add_param(
179 "timestamp".into(),
180 serde_json::to_value(self.timestamp).unwrap(),
181 )
182 }
183
184 fn session_id(&self) -> String {
185 self.session_id.clone()
186 }
187}
188
189impl AgentEvent for SessionStopEvent {
190 fn to_jsonrpc(&self) -> JsonRpcNotification {
191 JsonRpcNotification::builder()
192 .method("session.stop".into())
193 .add_param("session_id".into(), Value::from(self.session_id.clone()))
194 .add_param("success".into(), Value::from(self.success))
195 .add_param("result".into(), Value::from(self.result.clone()))
196 .add_param("error".into(), Value::from(self.error.clone()))
197 .add_param(
198 "timestamp".into(),
199 serde_json::to_value(self.timestamp).unwrap(),
200 )
201 .add_param("usage".into(), serde_json::to_value(self.usage).unwrap())
202 }
203
204 fn session_id(&self) -> String {
205 self.session_id.clone()
206 }
207}
208
209impl AgentEvent for UserPromptSubmitEvent {
210 fn to_jsonrpc(&self) -> JsonRpcNotification {
211 JsonRpcNotification::builder()
212 .method("user.prompt.submit".into())
213 .add_param("session_id".into(), Value::from(self.session_id.clone()))
214 .add_param("turn_id".into(), Value::from(self.turn_id.clone()))
215 .add_param("prompt".into(), Value::from(self.prompt.clone()))
216 .add_param(
217 "timestamp".into(),
218 serde_json::to_value(self.timestamp).unwrap(),
219 )
220 }
221
222 fn session_id(&self) -> String {
223 self.session_id.clone()
224 }
225}
226
227impl AgentEvent for StreamDeltaEvent {
228 fn to_jsonrpc(&self) -> JsonRpcNotification {
229 JsonRpcNotification::builder()
230 .method("stream.delta".into())
231 .add_param("session_id".into(), Value::from(self.session_id.clone()))
232 .add_param("turn_id".into(), Value::from(self.turn_id.clone()))
233 .add_param("delta".into(), Value::from(self.delta.clone()))
234 .add_param(
235 "delta_type".into(),
236 serde_json::to_value(self.delta_type.clone()).unwrap(),
237 )
238 .add_param(
239 "timestamp".into(),
240 serde_json::to_value(self.timestamp).unwrap(),
241 )
242 }
243
244 fn session_id(&self) -> String {
245 self.session_id.clone()
246 }
247}
248
249impl AgentEvent for ToolCallEvent {
250 fn to_jsonrpc(&self) -> JsonRpcNotification {
251 JsonRpcNotification::builder()
252 .method("tool.call".into())
253 .add_param("session_id".into(), Value::from(self.session_id.clone()))
254 .add_param("turn_id".into(), Value::from(self.turn_id.clone()))
255 .add_param("name".into(), Value::from(self.name.clone()))
256 .add_param("input".into(), self.input.clone())
257 .add_param(
258 "timestamp".into(),
259 serde_json::to_value(self.timestamp).unwrap(),
260 )
261 }
262
263 fn session_id(&self) -> String {
264 self.session_id.clone()
265 }
266}
267
268impl AgentEvent for ToolResultEvent {
269 fn to_jsonrpc(&self) -> JsonRpcNotification {
270 JsonRpcNotification::builder()
271 .method("tool.result".into())
272 .add_param("session_id".into(), Value::from(self.session_id.clone()))
273 .add_param("turn_id".into(), Value::from(self.turn_id.clone()))
274 .add_param("result".into(), serde_json::to_value(&self.result).unwrap())
275 .add_param(
276 "tool_call_id".into(),
277 Value::from(self.tool_call_id.clone()),
278 )
279 .add_param(
280 "timestamp".into(),
281 serde_json::to_value(self.timestamp).unwrap(),
282 )
283 }
284
285 fn session_id(&self) -> String {
286 self.session_id.clone()
287 }
288}
289
290impl AgentEvent for SkillLoadEvent {
291 fn to_jsonrpc(&self) -> JsonRpcNotification {
292 JsonRpcNotification::builder()
293 .method("skill.load".into())
294 .add_param("session_id".into(), Value::from(self.session_id.clone()))
295 .add_param("turn_id".into(), Value::from(self.turn_id.clone()))
296 .add_param("skill_name".into(), Value::from(self.skill_name.clone()))
297 .add_param(
298 "timestamp".into(),
299 serde_json::to_value(self.timestamp).unwrap(),
300 )
301 }
302
303 fn session_id(&self) -> String {
304 self.session_id.clone()
305 }
306}
307
308impl AgentEvent for AssistantResponseEvent {
309 fn to_jsonrpc(&self) -> JsonRpcNotification {
310 JsonRpcNotification::builder()
311 .method("assistant.response".into())
312 .add_param("session_id".into(), Value::from(self.session_id.clone()))
313 .add_param("turn_id".into(), Value::from(self.turn_id.clone()))
314 .add_param("full_text".into(), Value::from(self.full_text.clone()))
315 .add_param(
316 "tool_calls".into(),
317 Value::from(self.tool_calls.clone().map(|tcs| {
318 tcs.iter()
319 .map(|tc| serde_json::to_value(tc).unwrap())
320 .collect::<Vec<Value>>()
321 })),
322 )
323 .add_param(
324 "timestamp".into(),
325 serde_json::to_value(self.timestamp).unwrap(),
326 )
327 }
328
329 fn session_id(&self) -> String {
330 self.session_id.clone()
331 }
332}
333
334#[derive(Debug, Clone)]
336#[non_exhaustive]
337pub enum AgentEventAny {
338 SessionInit(SessionInitEvent),
339 SessionStop(SessionStopEvent),
340 StreamDelta(StreamDeltaEvent),
341 ToolCall(ToolCallEvent),
342 ToolResult(ToolResultEvent),
343 AssistantResponse(AssistantResponseEvent),
344 SkillLoad(SkillLoadEvent),
345 UserPromptSubmit(UserPromptSubmitEvent),
346}
347
348impl AgentEventAny {
349 pub fn timestamp(&self) -> DateTime<Utc> {
350 match self {
351 Self::SessionInit(s) => s.timestamp,
352 Self::AssistantResponse(s) => s.timestamp,
353 Self::SessionStop(s) => s.timestamp,
354 Self::SkillLoad(s) => s.timestamp,
355 Self::StreamDelta(s) => s.timestamp,
356 Self::UserPromptSubmit(s) => s.timestamp,
357 Self::ToolCall(s) => s.timestamp,
358 Self::ToolResult(s) => s.timestamp,
359 }
360 }
361}
362
363impl AgentEvent for AgentEventAny {
364 fn to_jsonrpc(&self) -> JsonRpcNotification {
365 match self {
366 Self::SessionInit(s) => s.to_jsonrpc(),
367 Self::AssistantResponse(s) => s.to_jsonrpc(),
368 Self::SessionStop(s) => s.to_jsonrpc(),
369 Self::ToolCall(s) => s.to_jsonrpc(),
370 Self::StreamDelta(s) => s.to_jsonrpc(),
371 Self::UserPromptSubmit(s) => s.to_jsonrpc(),
372 Self::ToolResult(s) => s.to_jsonrpc(),
373 Self::SkillLoad(s) => s.to_jsonrpc(),
374 }
375 }
376
377 fn session_id(&self) -> String {
378 match self {
379 Self::AssistantResponse(s) => s.session_id.clone(),
380 Self::SessionInit(s) => s.session_id.clone(),
381 Self::SessionStop(s) => s.session_id.clone(),
382 Self::StreamDelta(s) => s.session_id.clone(),
383 Self::SkillLoad(s) => s.session_id.clone(),
384 Self::ToolCall(s) => s.session_id.clone(),
385 Self::ToolResult(s) => s.session_id.clone(),
386 Self::UserPromptSubmit(s) => s.session_id.clone(),
387 }
388 }
389}
390
391impl TryFrom<JsonRpcNotification> for AgentEventAny {
392 type Error = AgentEventError;
393
394 fn try_from(value: JsonRpcNotification) -> Result<Self, Self::Error> {
395 let session_id = value
396 .params
397 .get("session_id")
398 .and_then(|v| v.as_str())
399 .ok_or_else(|| AgentEventError::MissingField("session_id".to_string()))?
400 .to_string();
401 let turn_id = value
402 .params
403 .get("turn_id")
404 .and_then(|v| v.as_str())
405 .unwrap_or("")
406 .to_string();
407
408 match value.method.as_str() {
409 "session.init" => Ok(Self::SessionInit(SessionInitEvent {
410 session_id,
411 model: value
412 .params
413 .get("model")
414 .and_then(|v| v.as_str())
415 .ok_or_else(|| AgentEventError::MissingField("model".to_string()))?
416 .to_string(),
417 provider: value
418 .params
419 .get("provider")
420 .and_then(|v| v.as_str())
421 .ok_or_else(|| AgentEventError::MissingField("provider".to_string()))?
422 .to_string(),
423 system: value
424 .params
425 .get("system")
426 .and_then(|v| v.as_str())
427 .ok_or_else(|| AgentEventError::MissingField("system".to_string()))?
428 .to_string(),
429 init_type: {
430 let raw = value
431 .params
432 .get("init_type")
433 .ok_or_else(|| AgentEventError::MissingField("init_type".to_string()))?;
434 let init_type: SessionInitType = serde_json::from_value(raw.to_owned())
435 .map_err(|_| AgentEventError::InvalidFieldType("init_type".to_string()))?;
436 init_type
437 },
438 timestamp: {
439 let raw = value
440 .params
441 .get("timestamp")
442 .ok_or_else(|| AgentEventError::MissingField("timestamp".to_string()))?;
443 let tms: DateTime<Utc> = serde_json::from_value(raw.to_owned())
444 .map_err(|_| AgentEventError::InvalidFieldType("timestamp".to_string()))?;
445 tms
446 },
447 })),
448 "session.stop" => Ok(Self::SessionStop(SessionStopEvent {
449 session_id,
450 success: value
451 .params
452 .get("success")
453 .and_then(|v| v.as_bool())
454 .ok_or_else(|| AgentEventError::InvalidFieldType("success".to_string()))?,
455 result: value
456 .params
457 .get("result")
458 .and_then(|v| v.as_str())
459 .map(|s| s.to_string()),
460 error: value
461 .params
462 .get("error")
463 .and_then(|v| v.as_str())
464 .map(|s| s.to_string()),
465 timestamp: {
466 let raw = value
467 .params
468 .get("timestamp")
469 .ok_or_else(|| AgentEventError::MissingField("timestamp".to_string()))?;
470 let tms: DateTime<Utc> = serde_json::from_value(raw.to_owned())
471 .map_err(|_| AgentEventError::InvalidFieldType("timestamp".to_string()))?;
472 tms
473 },
474 usage: {
475 let raw = value
476 .params
477 .get("usage")
478 .ok_or_else(|| AgentEventError::MissingField("usage".to_string()))?;
479 let usg: Usage = serde_json::from_value(raw.to_owned())
480 .map_err(|_| AgentEventError::InvalidFieldType("usage".to_string()))?;
481 usg
482 },
483 })),
484 "user.prompt.submit" => Ok(Self::UserPromptSubmit(UserPromptSubmitEvent {
485 session_id,
486 turn_id,
487 prompt: value
488 .params
489 .get("prompt")
490 .and_then(|v| v.as_str())
491 .ok_or_else(|| AgentEventError::MissingField("prompt".to_string()))?
492 .to_string(),
493 timestamp: {
494 let raw = value
495 .params
496 .get("timestamp")
497 .ok_or_else(|| AgentEventError::MissingField("timestamp".to_string()))?;
498 let tms: DateTime<Utc> = serde_json::from_value(raw.to_owned())
499 .map_err(|_| AgentEventError::InvalidFieldType("timestamp".to_string()))?;
500 tms
501 },
502 })),
503 "stream.delta" => Ok(Self::StreamDelta(StreamDeltaEvent {
504 session_id,
505 turn_id,
506 delta: value
507 .params
508 .get("delta")
509 .and_then(|v| v.as_str())
510 .ok_or_else(|| AgentEventError::MissingField("delta".to_string()))?
511 .to_string(),
512 delta_type: {
513 let raw = value
514 .params
515 .get("delta_type")
516 .ok_or_else(|| AgentEventError::MissingField("delta_type".into()))?;
517 let dt: DeltaType = serde_json::from_value(raw.to_owned())
518 .map_err(|_| AgentEventError::InvalidFieldType("delta_type".to_string()))?;
519 dt
520 },
521 timestamp: {
522 let raw = value
523 .params
524 .get("timestamp")
525 .ok_or_else(|| AgentEventError::MissingField("timestamp".to_string()))?;
526 let tms: DateTime<Utc> = serde_json::from_value(raw.to_owned())
527 .map_err(|_| AgentEventError::InvalidFieldType("timestamp".to_string()))?;
528 tms
529 },
530 })),
531 "tool.call" => Ok(Self::ToolCall(ToolCallEvent {
532 session_id,
533 turn_id,
534 name: value
535 .params
536 .get("name")
537 .and_then(|v| v.as_str())
538 .ok_or_else(|| AgentEventError::MissingField("name".to_string()))?
539 .to_string(),
540 input: value.params.get("input").cloned().unwrap_or(Value::Null),
541 timestamp: {
542 let raw = value
543 .params
544 .get("timestamp")
545 .ok_or_else(|| AgentEventError::MissingField("timestamp".to_string()))?;
546 let tms: DateTime<Utc> = serde_json::from_value(raw.to_owned())
547 .map_err(|_| AgentEventError::InvalidFieldType("timestamp".to_string()))?;
548 tms
549 },
550 })),
551 "tool.result" => {
552 let result = value
553 .params
554 .get("result")
555 .ok_or_else(|| AgentEventError::MissingField("result".to_string()))?;
556 let tool_result: ToolResult = serde_json::from_value(result.to_owned())
557 .map_err(|_| AgentEventError::InvalidFieldType("result".to_string()))?;
558 Ok(Self::ToolResult(ToolResultEvent {
559 session_id,
560 turn_id,
561 result: tool_result,
562 tool_call_id: value
563 .params
564 .get("tool_call_id")
565 .and_then(|v| v.as_str())
566 .ok_or_else(|| AgentEventError::MissingField("tool_call_id".to_string()))?
567 .to_string(),
568 timestamp: {
569 let raw = value.params.get("timestamp").ok_or_else(|| {
570 AgentEventError::MissingField("timestamp".to_string())
571 })?;
572 let tms: DateTime<Utc> =
573 serde_json::from_value(raw.to_owned()).map_err(|_| {
574 AgentEventError::InvalidFieldType("timestamp".to_string())
575 })?;
576 tms
577 },
578 }))
579 }
580 "skill.load" => Ok(Self::SkillLoad(SkillLoadEvent {
581 session_id,
582 turn_id,
583 skill_name: value
584 .params
585 .get("skill_name")
586 .and_then(|v| v.as_str())
587 .ok_or_else(|| AgentEventError::MissingField("skill_name".to_string()))?
588 .to_string(),
589 timestamp: {
590 let raw = value
591 .params
592 .get("timestamp")
593 .ok_or_else(|| AgentEventError::MissingField("timestamp".to_string()))?;
594 let tms: DateTime<Utc> = serde_json::from_value(raw.to_owned())
595 .map_err(|_| AgentEventError::InvalidFieldType("timestamp".to_string()))?;
596 tms
597 },
598 })),
599 "assistant.response" => {
600 let tool_calls = value.params.get("tool_calls").and_then(|v| match v {
601 Value::Array(arr) => arr
602 .iter()
603 .map(|a| {
604 let tc: Result<ToolCall, AgentEventError> =
605 serde_json::from_value(a.to_owned()).map_err(|_| {
606 AgentEventError::InvalidFieldType("tool_calls".into())
607 });
608 tc
609 })
610 .collect::<Result<Vec<_>, _>>()
611 .ok(),
612 _ => None,
613 });
614 Ok(Self::AssistantResponse(AssistantResponseEvent {
615 session_id,
616 turn_id,
617 full_text: value
618 .params
619 .get("full_text")
620 .and_then(|v| v.as_str())
621 .ok_or_else(|| AgentEventError::MissingField("full_text".to_string()))?
622 .to_string(),
623 tool_calls,
624 timestamp: {
625 let raw = value.params.get("timestamp").ok_or_else(|| {
626 AgentEventError::MissingField("timestamp".to_string())
627 })?;
628 let tms: DateTime<Utc> =
629 serde_json::from_value(raw.to_owned()).map_err(|_| {
630 AgentEventError::InvalidFieldType("timestamp".to_string())
631 })?;
632 tms
633 },
634 }))
635 }
636 method => Err(AgentEventError::UnknownMethod(method.to_string())),
637 }
638 }
639}
640
641#[cfg(test)]
642mod tests {
643 use super::*;
644 use serde_json::json;
645
646 #[test]
647 fn session_init_type_from_value() {
648 let start: SessionInitType = serde_json::from_value(Value::from("Start"))
649 .expect("Should be able to convert to SessionInitType");
650 let resume: SessionInitType = serde_json::from_value(Value::from("Resume"))
651 .expect("Should be able to convert to SessionInitType");
652 assert!(matches!(start, SessionInitType::Start));
653 assert!(matches!(resume, SessionInitType::Resume));
654 }
655
656 #[test]
657 fn session_init_type_from_str_ok() {
658 assert!(matches!(
659 SessionInitType::try_from("start"),
660 Ok(SessionInitType::Start)
661 ));
662 assert!(matches!(
663 SessionInitType::try_from("resume"),
664 Ok(SessionInitType::Resume)
665 ));
666 }
667
668 #[test]
669 fn session_init_type_from_str_err() {
670 let err = SessionInitType::try_from("unknown").unwrap_err();
671 assert!(matches!(err, AgentEventError::InvalidFieldType(_)));
672 assert!(
673 err.to_string()
674 .contains("No init type with message: unknown")
675 );
676 }
677
678 #[test]
679 fn delta_type_from_value() {
680 assert_eq!(Value::from(DeltaType::Text), Value::from("text"));
681 assert_eq!(Value::from(DeltaType::Thinking), Value::from("thinking"));
682 }
683
684 #[test]
685 fn session_init_event_to_jsonrpc() {
686 let event = SessionInitEvent {
687 session_id: "s1".into(),
688 model: "gpt-4".into(),
689 provider: "openai".into(),
690 system: "sys".into(),
691 init_type: SessionInitType::Start,
692 timestamp: Utc::now(),
693 };
694 let rpc = event.to_jsonrpc();
695 assert_eq!(rpc.method, "session.init");
696 assert_eq!(rpc.params.get("session_id"), Some(&Value::from("s1")));
697 assert_eq!(rpc.params.get("model"), Some(&Value::from("gpt-4")));
698 assert_eq!(rpc.params.get("provider"), Some(&Value::from("openai")));
699 assert_eq!(rpc.params.get("system"), Some(&Value::from("sys")));
700 assert_eq!(rpc.params.get("init_type"), Some(&Value::from("Start")));
701 }
702
703 #[test]
704 fn session_stop_event_to_jsonrpc() {
705 let event = SessionStopEvent {
706 session_id: "s1".into(),
707 success: true,
708 result: Some("done".into()),
709 error: None,
710 timestamp: Utc::now(),
711 usage: Usage::default(),
712 };
713 let rpc = event.to_jsonrpc();
714 assert_eq!(rpc.method, "session.stop");
715 assert_eq!(rpc.params.get("success"), Some(&Value::from(true)));
716 assert_eq!(rpc.params.get("result"), Some(&Value::from("done")));
717 assert_eq!(rpc.params.get("error"), Some(&Value::Null));
718 assert_eq!(
719 rpc.params.get("usage"),
720 Some(
721 &serde_json::to_value(Usage::default())
722 .expect("Should be able to convert to value")
723 )
724 );
725 }
726
727 #[test]
728 fn user_prompt_submit_event_to_jsonrpc() {
729 let event = UserPromptSubmitEvent {
730 session_id: "s1".into(),
731 turn_id: "t1".into(),
732 prompt: "hello".into(),
733 timestamp: Utc::now(),
734 };
735 let rpc = event.to_jsonrpc();
736 assert_eq!(rpc.method, "user.prompt.submit");
737 assert_eq!(rpc.params.get("prompt"), Some(&Value::from("hello")));
738 }
739
740 #[test]
741 fn stream_delta_event_to_jsonrpc() {
742 let event = StreamDeltaEvent {
743 session_id: "s1".into(),
744 turn_id: "t1".into(),
745 delta: "world".into(),
746 delta_type: DeltaType::Thinking,
747 timestamp: Utc::now(),
748 };
749 let rpc = event.to_jsonrpc();
750 assert_eq!(rpc.method, "stream.delta");
751 assert_eq!(rpc.params.get("delta"), Some(&Value::from("world")));
752 assert_eq!(rpc.params.get("delta_type"), Some(&Value::from("Thinking")));
753 }
754
755 #[test]
756 fn tool_call_event_to_jsonrpc() {
757 let event = ToolCallEvent {
758 session_id: "s1".into(),
759 turn_id: "t1".into(),
760 name: "read".into(),
761 input: json!({"path": "/tmp"}),
762 timestamp: Utc::now(),
763 };
764 let rpc = event.to_jsonrpc();
765 assert_eq!(rpc.method, "tool.call");
766 assert_eq!(rpc.params.get("name"), Some(&Value::from("read")));
767 assert_eq!(rpc.params.get("input"), Some(&json!({"path": "/tmp"})));
768 }
769
770 #[test]
771 fn tool_result_event_to_jsonrpc() {
772 let event = ToolResultEvent {
773 session_id: "s1".into(),
774 turn_id: "t1".into(),
775 result: ToolResult::Ok("ok".into()),
776 tool_call_id: "tc1".into(),
777 timestamp: Utc::now(),
778 };
779 let rpc = event.to_jsonrpc();
780 assert_eq!(rpc.method, "tool.result");
781 assert_eq!(rpc.params.get("tool_call_id"), Some(&Value::from("tc1")));
782 assert_eq!(rpc.params.get("result"), Some(&json!({"Ok": "ok"})));
783 }
784
785 #[test]
786 fn skill_load_event_to_jsonrpc() {
787 let event = SkillLoadEvent {
788 session_id: "s1".into(),
789 turn_id: "t1".into(),
790 skill_name: "coding".into(),
791 timestamp: Utc::now(),
792 };
793 let rpc = event.to_jsonrpc();
794 assert_eq!(rpc.method, "skill.load");
795 assert_eq!(rpc.params.get("skill_name"), Some(&Value::from("coding")));
796 }
797
798 #[test]
799 fn assistant_response_event_to_jsonrpc() {
800 let event = AssistantResponseEvent {
801 session_id: "s1".into(),
802 turn_id: "t1".into(),
803 full_text: "hi".into(),
804 tool_calls: None,
805 timestamp: Utc::now(),
806 };
807 let rpc = event.to_jsonrpc();
808 assert_eq!(rpc.method, "assistant.response");
809 assert_eq!(rpc.params.get("full_text"), Some(&Value::from("hi")));
810 assert_eq!(rpc.params.get("tool_calls"), Some(&Value::Null));
811 }
812
813 #[test]
814 fn agent_event_any_session_id() {
815 let event = AgentEventAny::SessionInit(SessionInitEvent {
816 session_id: "sid".into(),
817 model: "m".into(),
818 provider: "p".into(),
819 system: "s".into(),
820 init_type: SessionInitType::Start,
821 timestamp: Utc::now(),
822 });
823 assert_eq!(event.session_id(), "sid");
824 }
825
826 #[test]
827 fn agent_event_any_to_jsonrpc_roundtrip() {
828 let event = AgentEventAny::UserPromptSubmit(UserPromptSubmitEvent {
829 session_id: "s1".into(),
830 turn_id: "t1".into(),
831 prompt: "p".into(),
832 timestamp: Utc::now(),
833 });
834 let rpc = event.to_jsonrpc();
835 assert_eq!(rpc.method, "user.prompt.submit");
836 assert_eq!(rpc.params.get("session_id"), Some(&Value::from("s1")));
837 }
838
839 #[test]
840 fn try_from_jsonrpc_session_init_ok() {
841 let rpc = JsonRpcNotification::builder()
842 .method("session.init".into())
843 .add_param("session_id".into(), Value::from("s1"))
844 .add_param("model".into(), Value::from("gpt-4"))
845 .add_param("provider".into(), Value::from("openai"))
846 .add_param("system".into(), Value::from("sys"))
847 .add_param("init_type".into(), Value::from("Resume"))
848 .add_param("timestamp".into(), {
849 let tms = Utc::now();
850 serde_json::to_value(tms).expect("Should convert to value")
851 });
852 let any = AgentEventAny::try_from(rpc).unwrap();
853 assert!(
854 matches!(any, AgentEventAny::SessionInit(ref e) if e.session_id == "s1" && matches!(e.init_type, SessionInitType::Resume))
855 );
856 }
857
858 #[test]
859 fn try_from_jsonrpc_session_init_missing_field() {
860 let rpc = JsonRpcNotification::builder()
861 .method("session.init".into())
862 .add_param("session_id".into(), Value::from("s1"))
863 .add_param("timestamp".into(), {
864 let tms = Utc::now();
865 serde_json::to_value(tms).expect("Should convert to value")
866 });
867 let err = AgentEventAny::try_from(rpc).unwrap_err();
868 assert!(matches!(err, AgentEventError::MissingField(_)));
869 }
870
871 #[test]
872 fn try_from_jsonrpc_session_init_invalid_init_type() {
873 let rpc = JsonRpcNotification::builder()
874 .method("session.init".into())
875 .add_param("session_id".into(), Value::from("s1"))
876 .add_param("model".into(), Value::from("gpt-4"))
877 .add_param("provider".into(), Value::from("openai"))
878 .add_param("system".into(), Value::from("sys"))
879 .add_param("init_type".into(), Value::from("invalid"))
880 .add_param("timestamp".into(), {
881 let tms = Utc::now();
882 serde_json::to_value(tms).expect("Should convert to value")
883 });
884 let err = AgentEventAny::try_from(rpc).unwrap_err();
885 assert!(matches!(err, AgentEventError::InvalidFieldType(_)));
886 }
887
888 #[test]
889 fn try_from_jsonrpc_session_stop_ok() {
890 let rpc = JsonRpcNotification::builder()
891 .method("session.stop".into())
892 .add_param("session_id".into(), Value::from("s1"))
893 .add_param("success".into(), Value::from(true))
894 .add_param("result".into(), Value::from("done"))
895 .add_param("error".into(), Value::Null)
896 .add_param("timestamp".into(), {
897 let tms = Utc::now();
898 serde_json::to_value(tms).expect("Should convert to value")
899 })
900 .add_param("usage".into(), {
901 let usg = Usage::default();
902 serde_json::to_value(usg).expect("Should convert to value")
903 });
904 let any = AgentEventAny::try_from(rpc).unwrap();
905 assert!(
906 matches!(any, AgentEventAny::SessionStop(ref e) if e.success && e.result == Some("done".into()) && e.error.is_none() && e.usage.latency == 0)
907 );
908 }
909
910 #[test]
911 fn try_from_jsonrpc_user_prompt_submit_ok() {
912 let rpc = JsonRpcNotification::builder()
913 .method("user.prompt.submit".into())
914 .add_param("session_id".into(), Value::from("s1"))
915 .add_param("turn_id".into(), Value::from("t1"))
916 .add_param("prompt".into(), Value::from("hello"))
917 .add_param("timestamp".into(), {
918 let tms = Utc::now();
919 serde_json::to_value(tms).expect("Should convert to value")
920 });
921 let any = AgentEventAny::try_from(rpc).unwrap();
922 assert!(matches!(any, AgentEventAny::UserPromptSubmit(ref e) if e.prompt == "hello"));
923 }
924
925 #[test]
926 fn try_from_jsonrpc_stream_delta_no_default() {
927 let rpc = JsonRpcNotification::builder()
928 .method("stream.delta".into())
929 .add_param("session_id".into(), Value::from("s1"))
930 .add_param("turn_id".into(), Value::from("t1"))
931 .add_param("delta".into(), Value::from("d"))
932 .add_param("timestamp".into(), {
933 let tms = Utc::now();
934 serde_json::to_value(tms).expect("Should convert to value")
935 });
936 let any = AgentEventAny::try_from(rpc);
937 assert!(any.is_err_and(
938 |e| matches!(e, AgentEventError::MissingField(ref err) if err == "delta_type")
939 ));
940 }
941
942 #[test]
943 fn try_from_jsonrpc_stream_delta_thinking() {
944 let rpc = JsonRpcNotification::builder()
945 .method("stream.delta".into())
946 .add_param("session_id".into(), Value::from("s1"))
947 .add_param("turn_id".into(), Value::from("t1"))
948 .add_param("delta".into(), Value::from("d"))
949 .add_param(
950 "delta_type".into(),
951 serde_json::to_value(DeltaType::Thinking)
952 .expect("Should be able to convert to value"),
953 )
954 .add_param("timestamp".into(), {
955 let tms = Utc::now();
956 serde_json::to_value(tms).expect("Should convert to value")
957 });
958 let any = AgentEventAny::try_from(rpc).unwrap();
959 assert!(
960 matches!(any, AgentEventAny::StreamDelta(ref e) if matches!(e.delta_type, DeltaType::Thinking))
961 );
962 }
963
964 #[test]
965 fn try_from_jsonrpc_tool_call_ok() {
966 let rpc = JsonRpcNotification::builder()
967 .method("tool.call".into())
968 .add_param("session_id".into(), Value::from("s1"))
969 .add_param("turn_id".into(), Value::from("t1"))
970 .add_param("name".into(), Value::from("read"))
971 .add_param("input".into(), json!({"path": "/tmp"}))
972 .add_param("timestamp".into(), {
973 let tms = Utc::now();
974 serde_json::to_value(tms).expect("Should convert to value")
975 });
976 let any = AgentEventAny::try_from(rpc).unwrap();
977 assert!(matches!(any, AgentEventAny::ToolCall(ref e) if e.name == "read"));
978 }
979
980 #[test]
981 fn try_from_jsonrpc_tool_result_ok() {
982 let rpc = JsonRpcNotification::builder()
983 .method("tool.result".into())
984 .add_param("session_id".into(), Value::from("s1"))
985 .add_param("turn_id".into(), Value::from("t1"))
986 .add_param("tool_call_id".into(), Value::from("tc1"))
987 .add_param("result".into(), json!({"Ok": "ok"}))
988 .add_param("timestamp".into(), {
989 let tms = Utc::now();
990 serde_json::to_value(tms).expect("Should convert to value")
991 });
992 let any = AgentEventAny::try_from(rpc).unwrap();
993 assert!(
994 matches!(any, AgentEventAny::ToolResult(ref e) if matches!(e.result, ToolResult::Ok(ref s) if s == "ok"))
995 );
996 }
997
998 #[test]
999 fn try_from_jsonrpc_skill_load_ok() {
1000 let rpc = JsonRpcNotification::builder()
1001 .method("skill.load".into())
1002 .add_param("session_id".into(), Value::from("s1"))
1003 .add_param("turn_id".into(), Value::from("t1"))
1004 .add_param("skill_name".into(), Value::from("coding"))
1005 .add_param("timestamp".into(), {
1006 let tms = Utc::now();
1007 serde_json::to_value(tms).expect("Should convert to value")
1008 });
1009 let any = AgentEventAny::try_from(rpc).unwrap();
1010 assert!(matches!(any, AgentEventAny::SkillLoad(ref e) if e.skill_name == "coding"));
1011 }
1012
1013 #[test]
1014 fn try_from_jsonrpc_assistant_response_ok() {
1015 let rpc = JsonRpcNotification::builder()
1016 .method("assistant.response".into())
1017 .add_param("session_id".into(), Value::from("s1"))
1018 .add_param("turn_id".into(), Value::from("t1"))
1019 .add_param("full_text".into(), Value::from("hi"))
1020 .add_param("timestamp".into(), {
1021 let tms = Utc::now();
1022 serde_json::to_value(tms).expect("Should convert to value")
1023 });
1024 let any = AgentEventAny::try_from(rpc).unwrap();
1025 assert!(
1026 matches!(any, AgentEventAny::AssistantResponse(ref e) if e.full_text == "hi" && e.tool_calls.is_none())
1027 );
1028 }
1029
1030 #[test]
1031 fn try_from_jsonrpc_assistant_response_with_tool_calls() {
1032 let rpc = JsonRpcNotification::builder()
1033 .method("assistant.response".into())
1034 .add_param("session_id".into(), Value::from("s1"))
1035 .add_param("turn_id".into(), Value::from("t1"))
1036 .add_param("full_text".into(), Value::from("hi"))
1037 .add_param("tool_calls".into(), json!([{"call_type":"function","id":"1","function":{"name":"tool","arguments":"{}"}}]))
1038 .add_param("timestamp".into(), {
1039 let tms = Utc::now();
1040 serde_json::to_value(tms).expect("Should convert to value")
1041 });
1042 let any = AgentEventAny::try_from(rpc).unwrap();
1043 assert!(matches!(any, AgentEventAny::AssistantResponse(ref e) if e.tool_calls.is_some()));
1044 }
1045
1046 #[test]
1047 fn try_from_jsonrpc_unknown_method() {
1048 let rpc = JsonRpcNotification::builder()
1049 .method("unknown".into())
1050 .add_param("session_id".into(), Value::from("s1"))
1051 .add_param("timestamp".into(), {
1052 let tms = Utc::now();
1053 serde_json::to_value(tms).expect("Should convert to value")
1054 });
1055 let err = AgentEventAny::try_from(rpc).unwrap_err();
1056 assert!(matches!(err, AgentEventError::UnknownMethod(ref m) if m == "unknown"));
1057 }
1058
1059 #[test]
1060 fn try_from_jsonrpc_missing_session_id() {
1061 let rpc = JsonRpcNotification::builder()
1062 .method("session.stop".into())
1063 .add_param("success".into(), Value::from(true))
1064 .add_param("timestamp".into(), {
1065 let tms = Utc::now();
1066 serde_json::to_value(tms).expect("Should convert to value")
1067 });
1068 let err = AgentEventAny::try_from(rpc).unwrap_err();
1069 assert!(matches!(err, AgentEventError::MissingField(ref m) if m == "session_id"));
1070 }
1071}