1use serde::de::DeserializeOwned;
20use serde::{Deserialize, Serialize};
21use serde_json::Value;
22
23use crate::events::Dispatch;
24
25pub trait TypedEvent {
31 type Payload: Serialize + DeserializeOwned + Clone + Send + Sync + 'static;
33 const NAME: &'static str;
35 const MODE: Dispatch;
37 const AROUND: bool;
39}
40
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
48pub struct AgentAdmitPayload {
49 pub tenant_id: String,
50 #[serde(default)]
51 pub monthly: u64,
52 #[serde(default)]
53 pub daily: u64,
54 #[serde(default)]
55 pub requests_per_month: Option<u64>,
56 #[serde(default)]
57 pub requests_per_day: Option<u64>,
58 pub tier: String,
59}
60
61#[derive(Debug, Clone, Copy)]
64pub struct AgentAdmitEvent;
65impl TypedEvent for AgentAdmitEvent {
66 type Payload = AgentAdmitPayload;
67 const NAME: &'static str = crate::events_catalog::ev::AGENT_ADMIT;
68 const MODE: Dispatch = Dispatch::Bail;
69 const AROUND: bool = false;
70}
71
72#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
78pub struct AgentStartedPayload {
79 pub agent_name: String,
80 #[serde(default)]
81 pub run_id: String,
82 #[serde(default)]
83 pub tenant: String,
84 #[serde(default)]
85 pub event: String,
86}
87
88#[derive(Debug, Clone, Copy)]
90pub struct AgentStartedEvent;
91impl TypedEvent for AgentStartedEvent {
92 type Payload = AgentStartedPayload;
93 const NAME: &'static str = crate::events_catalog::ev::AGENT_STARTED;
94 const MODE: Dispatch = Dispatch::Parallel;
95 const AROUND: bool = false;
96}
97
98#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub struct AgentUsagePayload {
106 #[serde(default)]
107 pub tenant: Option<String>,
108 #[serde(default)]
109 pub prompt: i64,
110 #[serde(default)]
111 pub completion: i64,
112 #[serde(default)]
113 pub total: i64,
114}
115
116#[derive(Debug, Clone, Copy)]
118pub struct AgentUsageEvent;
119impl TypedEvent for AgentUsageEvent {
120 type Payload = AgentUsagePayload;
121 const NAME: &'static str = crate::events_catalog::ev::AGENT_USAGE;
122 const MODE: Dispatch = Dispatch::Emit;
123 const AROUND: bool = false;
124}
125
126#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
132pub struct AgentCompletedPayload {
133 pub agent_name: String,
134 #[serde(default)]
135 pub run_id: String,
136 #[serde(default = "default_status")]
137 pub status: String,
138 #[serde(default)]
139 pub event: String,
140}
141
142fn default_status() -> String {
143 "unknown".to_string()
144}
145
146#[derive(Debug, Clone, Copy)]
148pub struct AgentCompletedEvent;
149impl TypedEvent for AgentCompletedEvent {
150 type Payload = AgentCompletedPayload;
151 const NAME: &'static str = crate::events_catalog::ev::AGENT_COMPLETED;
152 const MODE: Dispatch = Dispatch::Emit;
153 const AROUND: bool = false;
154}
155
156#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
158pub struct AgentFailedPayload {
159 pub agent_name: String,
160 #[serde(default)]
161 pub run_id: String,
162 #[serde(default)]
163 pub tenant: String,
164 #[serde(default)]
165 pub event: String,
166}
167
168#[derive(Debug, Clone, Copy)]
171pub struct AgentFailedEvent;
172impl TypedEvent for AgentFailedEvent {
173 type Payload = AgentFailedPayload;
174 const NAME: &'static str = crate::events_catalog::ev::AGENT_FAILED;
175 const MODE: Dispatch = Dispatch::Emit;
176 const AROUND: bool = false;
177}
178
179#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
186pub struct AgentRunRequest {
187 #[serde(default)]
188 pub agent_name: String,
189 #[serde(default)]
190 pub message: String,
191}
192
193#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
197pub struct AgentRunResult {
198 #[serde(default)]
199 pub content: Value,
200 #[serde(default)]
201 pub source: Value,
202 #[serde(default)]
203 pub agent_name: String,
204 #[serde(default)]
205 pub run_id: String,
206 #[serde(default)]
207 pub usage: Option<Value>,
208 #[serde(default)]
209 pub metadata: Option<Value>,
210}
211
212#[derive(Debug, Clone, Copy)]
215pub struct AgentRunEvent;
216impl TypedEvent for AgentRunEvent {
217 type Payload = AgentRunRequest;
218 const NAME: &'static str = crate::events_catalog::ev::AGENT_RUN;
219 const MODE: Dispatch = Dispatch::Waterfall;
220 const AROUND: bool = true;
221}
222
223#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
229pub struct LlmCompleteRequest {
230 #[serde(default)]
231 pub prompt: String,
232}
233
234#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
236pub struct LlmCompleteResult {
237 #[serde(default)]
238 pub prompt: String,
239 #[serde(default)]
240 pub content: Value,
241}
242
243#[derive(Debug, Clone, Copy)]
247pub struct LlmCompleteEvent;
248impl TypedEvent for LlmCompleteEvent {
249 type Payload = LlmCompleteRequest;
250 const NAME: &'static str = crate::events_catalog::ev::LLM_COMPLETE;
251 const MODE: Dispatch = Dispatch::Waterfall;
252 const AROUND: bool = true;
253}
254
255#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
262pub struct LlmGetClientPayload {
263 pub capability: String,
264 #[serde(default)]
265 pub deny: Option<bool>,
266 #[serde(default)]
267 pub model: Option<String>,
268}
269
270#[derive(Debug, Clone, Copy)]
273pub struct LlmGetClientEvent;
274impl TypedEvent for LlmGetClientEvent {
275 type Payload = LlmGetClientPayload;
276 const NAME: &'static str = crate::events_catalog::ev::LLM_GET_CLIENT;
277 const MODE: Dispatch = Dispatch::Waterfall;
278 const AROUND: bool = true;
279}
280
281#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
289pub struct LlmMessage {
290 pub role: String,
291 #[serde(default)]
292 pub content: Value,
293}
294
295#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
297pub struct LlmGeneratePayload {
298 #[serde(default)]
299 pub messages: Vec<LlmMessage>,
300}
301
302#[derive(Debug, Clone, Copy)]
305pub struct LlmGenerateEvent;
306impl TypedEvent for LlmGenerateEvent {
307 type Payload = LlmGeneratePayload;
308 const NAME: &'static str = crate::events_catalog::ev::LLM_GENERATE;
309 const MODE: Dispatch = Dispatch::Waterfall;
310 const AROUND: bool = true;
311}
312
313#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
317pub struct LlmGenerateToolsPayload {
318 #[serde(default)]
319 pub messages: Vec<Value>,
320 #[serde(default)]
321 pub tools: Vec<Value>,
322}
323
324#[derive(Debug, Clone, Copy)]
327pub struct LlmGenerateToolsEvent;
328impl TypedEvent for LlmGenerateToolsEvent {
329 type Payload = LlmGenerateToolsPayload;
330 const NAME: &'static str = crate::events_catalog::ev::LLM_GENERATE_TOOLS;
331 const MODE: Dispatch = Dispatch::Waterfall;
332 const AROUND: bool = true;
333}
334
335#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
341pub struct ToolsExecutePayload {
342 pub name: String,
343 #[serde(default)]
344 pub args: Value,
345}
346
347#[derive(Debug, Clone, Copy)]
350pub struct ToolsExecuteEvent;
351impl TypedEvent for ToolsExecuteEvent {
352 type Payload = ToolsExecutePayload;
353 const NAME: &'static str = crate::events_catalog::ev::TOOLS_EXECUTE;
354 const MODE: Dispatch = Dispatch::Waterfall;
355 const AROUND: bool = true;
356}
357
358#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
360pub struct ToolsListRequest {
361 #[serde(default)]
362 pub tenant: Option<String>,
363}
364
365#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
368pub struct ToolsListResult {
369 #[serde(default)]
370 pub tenant: Option<String>,
371 #[serde(default)]
372 pub tools: Vec<Value>,
373}
374
375#[derive(Debug, Clone, Copy)]
378pub struct ToolsListEvent;
379impl TypedEvent for ToolsListEvent {
380 type Payload = ToolsListRequest;
381 const NAME: &'static str = crate::events_catalog::ev::TOOLS_LIST;
382 const MODE: Dispatch = Dispatch::Waterfall;
383 const AROUND: bool = true;
384}
385
386#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
389pub struct ToolsResolveRequest {
390 pub name: String,
391 #[serde(default)]
392 pub tenant: Option<String>,
393}
394
395#[derive(Debug, Clone, Copy)]
398pub struct ToolsResolveEvent;
399impl TypedEvent for ToolsResolveEvent {
400 type Payload = ToolsResolveRequest;
401 const NAME: &'static str = crate::events_catalog::ev::TOOLS_RESOLVE;
402 const MODE: Dispatch = Dispatch::Waterfall;
403 const AROUND: bool = true;
404}
405
406#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
413pub struct SchedulerBeforeRunPayload {
414 #[serde(default)]
415 pub agent_name: String,
416 #[serde(default)]
417 pub run_id: String,
418 #[serde(default)]
419 pub tenant: Option<String>,
420}
421
422#[derive(Debug, Clone, Copy)]
425pub struct SchedulerBeforeRunEvent;
426impl TypedEvent for SchedulerBeforeRunEvent {
427 type Payload = SchedulerBeforeRunPayload;
428 const NAME: &'static str = crate::events_catalog::ev::SCHEDULER_BEFORE_RUN;
429 const MODE: Dispatch = Dispatch::Waterfall;
430 const AROUND: bool = true;
431}
432
433#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
436pub struct SchedulerAdmitPayload {
437 #[serde(default)]
438 pub agent_name: String,
439 #[serde(default)]
440 pub deny: Option<bool>,
441}
442
443#[derive(Debug, Clone, Copy)]
445pub struct SchedulerAdmitEvent;
446impl TypedEvent for SchedulerAdmitEvent {
447 type Payload = SchedulerAdmitPayload;
448 const NAME: &'static str = crate::events_catalog::ev::SCHEDULER_ADMIT;
449 const MODE: Dispatch = Dispatch::Bail;
450 const AROUND: bool = false;
451}
452
453#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
460pub struct ServiceChangedPayload {
461 pub type_id: String,
462 #[serde(default)]
463 pub event: String,
464}
465
466#[derive(Debug, Clone, Default, Serialize, Deserialize)]
472pub struct SchedulerTickPayload {
473 #[serde(default)]
474 pub due_count: u64,
475 #[serde(default)]
476 pub catchup_count: u64,
477}
478
479#[derive(Debug, Clone, Copy)]
482pub struct SchedulerTickEvent;
483impl TypedEvent for SchedulerTickEvent {
484 type Payload = SchedulerTickPayload;
485 const NAME: &'static str = crate::events_catalog::ev::SCHEDULER_TICK;
486 const MODE: Dispatch = Dispatch::Emit;
487 const AROUND: bool = false;
488}
489
490#[derive(Debug, Clone, Default, Serialize, Deserialize)]
494pub struct ScheduleDispatchedPayload {
495 pub schedule_id: String,
496 #[serde(default)]
497 pub agent_name: String,
498 #[serde(default)]
499 pub tenant_id: String,
500 #[serde(default)]
501 pub is_catchup: bool,
502 #[serde(default)]
503 pub ok: bool,
504 #[serde(default)]
505 pub denied: bool,
506 #[serde(default)]
507 pub error: Option<String>,
508}
509
510#[derive(Debug, Clone, Copy)]
513pub struct ScheduleDispatchedEvent;
514impl TypedEvent for ScheduleDispatchedEvent {
515 type Payload = ScheduleDispatchedPayload;
516 const NAME: &'static str = crate::events_catalog::ev::SCHEDULER_SCHEDULE_DISPATCHED;
517 const MODE: Dispatch = Dispatch::Emit;
518 const AROUND: bool = false;
519}
520
521#[derive(Debug, Clone, Serialize, Deserialize)]
523pub struct PipelineStepStartedPayload {
524 pub pipeline_id: String,
525 pub target_agent: String,
526 #[serde(default)]
527 pub tenant_id: String,
528 #[serde(default)]
529 pub run_id: String,
530}
531
532#[derive(Debug, Clone, Copy)]
535pub struct PipelineStepStartedEvent;
536impl TypedEvent for PipelineStepStartedEvent {
537 type Payload = PipelineStepStartedPayload;
538 const NAME: &'static str = crate::events_catalog::ev::PIPELINE_STEP_STARTED;
539 const MODE: Dispatch = Dispatch::Emit;
540 const AROUND: bool = false;
541}
542
543#[derive(Debug, Clone, Serialize, Deserialize)]
545pub struct PipelineStepFinishedPayload {
546 pub pipeline_id: String,
547 pub target_agent: String,
548 #[serde(default)]
549 pub tenant_id: String,
550 #[serde(default)]
551 pub status: String,
552 #[serde(default)]
553 pub duration_ms: u64,
554 #[serde(default)]
555 pub error: Option<String>,
556}
557
558#[derive(Debug, Clone, Copy)]
561pub struct PipelineStepFinishedEvent;
562impl TypedEvent for PipelineStepFinishedEvent {
563 type Payload = PipelineStepFinishedPayload;
564 const NAME: &'static str = crate::events_catalog::ev::PIPELINE_STEP_FINISHED;
565 const MODE: Dispatch = Dispatch::Emit;
566 const AROUND: bool = false;
567}
568
569#[derive(Debug, Clone, Default, Serialize, Deserialize)]
571pub struct PipelineFanoutCompletedPayload {
572 #[serde(default)]
573 pub source_agent: String,
574 #[serde(default)]
575 pub tenant_id: String,
576 #[serde(default)]
577 pub triggered: Vec<String>,
578}
579
580#[derive(Debug, Clone, Copy)]
583pub struct PipelineFanoutCompletedEvent;
584impl TypedEvent for PipelineFanoutCompletedEvent {
585 type Payload = PipelineFanoutCompletedPayload;
586 const NAME: &'static str = crate::events_catalog::ev::PIPELINE_FANOUT_COMPLETED;
587 const MODE: Dispatch = Dispatch::Emit;
588 const AROUND: bool = false;
589}
590
591#[derive(Debug, Clone, Serialize, Deserialize)]
593pub struct TriggerFiredPayload {
594 pub trigger_id: String,
595 #[serde(default)]
596 pub event_type: String,
597 #[serde(default)]
598 pub target_agent: String,
599 #[serde(default)]
600 pub tenant_id: String,
601}
602
603#[derive(Debug, Clone, Copy)]
606pub struct TriggerFiredEvent;
607impl TypedEvent for TriggerFiredEvent {
608 type Payload = TriggerFiredPayload;
609 const NAME: &'static str = crate::events_catalog::ev::TRIGGER_FIRED;
610 const MODE: Dispatch = Dispatch::Emit;
611 const AROUND: bool = false;
612}
613
614#[derive(Debug, Clone, Copy)]
617pub struct ServiceChangedEvent;
618impl TypedEvent for ServiceChangedEvent {
619 type Payload = ServiceChangedPayload;
620 const NAME: &'static str = crate::events_catalog::ev::SERVICE_CHANGED;
621 const MODE: Dispatch = Dispatch::Emit;
622 const AROUND: bool = false;
623}
624
625#[cfg(test)]
626mod tests {
627 use super::*;
628 use crate::events_catalog::{contract_for, CONTRACTS};
629
630 #[test]
633 fn typed_events_match_catalog_contracts() {
634 let bindings: &[(&'static str, Dispatch, bool)] = &[
635 (
636 AgentAdmitEvent::NAME,
637 AgentAdmitEvent::MODE,
638 AgentAdmitEvent::AROUND,
639 ),
640 (
641 AgentCompletedEvent::NAME,
642 AgentCompletedEvent::MODE,
643 AgentCompletedEvent::AROUND,
644 ),
645 (
646 AgentFailedEvent::NAME,
647 AgentFailedEvent::MODE,
648 AgentFailedEvent::AROUND,
649 ),
650 (
651 AgentRunEvent::NAME,
652 AgentRunEvent::MODE,
653 AgentRunEvent::AROUND,
654 ),
655 (
656 AgentStartedEvent::NAME,
657 AgentStartedEvent::MODE,
658 AgentStartedEvent::AROUND,
659 ),
660 (
661 AgentUsageEvent::NAME,
662 AgentUsageEvent::MODE,
663 AgentUsageEvent::AROUND,
664 ),
665 (
666 LlmCompleteEvent::NAME,
667 LlmCompleteEvent::MODE,
668 LlmCompleteEvent::AROUND,
669 ),
670 (
671 LlmGetClientEvent::NAME,
672 LlmGetClientEvent::MODE,
673 LlmGetClientEvent::AROUND,
674 ),
675 (
676 LlmGenerateEvent::NAME,
677 LlmGenerateEvent::MODE,
678 LlmGenerateEvent::AROUND,
679 ),
680 (
681 LlmGenerateToolsEvent::NAME,
682 LlmGenerateToolsEvent::MODE,
683 LlmGenerateToolsEvent::AROUND,
684 ),
685 (
686 SchedulerAdmitEvent::NAME,
687 SchedulerAdmitEvent::MODE,
688 SchedulerAdmitEvent::AROUND,
689 ),
690 (
691 SchedulerBeforeRunEvent::NAME,
692 SchedulerBeforeRunEvent::MODE,
693 SchedulerBeforeRunEvent::AROUND,
694 ),
695 (
696 ServiceChangedEvent::NAME,
697 ServiceChangedEvent::MODE,
698 ServiceChangedEvent::AROUND,
699 ),
700 (
701 ToolsExecuteEvent::NAME,
702 ToolsExecuteEvent::MODE,
703 ToolsExecuteEvent::AROUND,
704 ),
705 (
706 ToolsListEvent::NAME,
707 ToolsListEvent::MODE,
708 ToolsListEvent::AROUND,
709 ),
710 (
711 ToolsResolveEvent::NAME,
712 ToolsResolveEvent::MODE,
713 ToolsResolveEvent::AROUND,
714 ),
715 (
716 SchedulerTickEvent::NAME,
717 SchedulerTickEvent::MODE,
718 SchedulerTickEvent::AROUND,
719 ),
720 (
721 ScheduleDispatchedEvent::NAME,
722 ScheduleDispatchedEvent::MODE,
723 ScheduleDispatchedEvent::AROUND,
724 ),
725 (
726 PipelineStepStartedEvent::NAME,
727 PipelineStepStartedEvent::MODE,
728 PipelineStepStartedEvent::AROUND,
729 ),
730 (
731 PipelineStepFinishedEvent::NAME,
732 PipelineStepFinishedEvent::MODE,
733 PipelineStepFinishedEvent::AROUND,
734 ),
735 (
736 PipelineFanoutCompletedEvent::NAME,
737 PipelineFanoutCompletedEvent::MODE,
738 PipelineFanoutCompletedEvent::AROUND,
739 ),
740 (
741 TriggerFiredEvent::NAME,
742 TriggerFiredEvent::MODE,
743 TriggerFiredEvent::AROUND,
744 ),
745 ];
746 for (name, mode, around) in bindings {
747 let contract = contract_for(name)
748 .unwrap_or_else(|| panic!("typed binding {name} missing from catalog"));
749 assert_eq!(&contract.mode, mode, "mode drift for {name}");
750 assert_eq!(&contract.around, around, "around drift for {name}");
751 }
752 assert_eq!(
753 bindings.len(),
754 CONTRACTS.len(),
755 "every catalog event must have exactly one typed binding"
756 );
757 }
758
759 #[test]
763 fn payload_round_trip_and_defaults() {
764 let started = AgentStartedPayload {
765 agent_name: "a".into(),
766 run_id: "r".into(),
767 tenant: "t".into(),
768 event: crate::events_catalog::ev::AGENT_STARTED.into(),
769 };
770 let v = serde_json::to_value(&started).unwrap();
771 assert_eq!(v.get("agent_name").and_then(Value::as_str), Some("a"));
772 let back: AgentStartedPayload = serde_json::from_value(v).unwrap();
773 assert_eq!(back, started);
774
775 let minimal: AgentCompletedPayload =
776 serde_json::from_value(serde_json::json!({ "agent_name": "x" })).unwrap();
777 assert_eq!(minimal.status, "unknown");
778 assert_eq!(minimal.run_id, "");
779
780 let usage = AgentUsagePayload {
781 tenant: None,
782 prompt: 3,
783 completion: 4,
784 total: 7,
785 };
786 let v = serde_json::to_value(&usage).unwrap();
787 assert!(v.get("tenant").map(Value::is_null).unwrap_or(false));
788 let back: AgentUsagePayload = serde_json::from_value(v).unwrap();
789 assert_eq!(back.prompt, 3);
790 assert!(back.tenant.is_none());
791 }
792}