1use std::{
2 borrow::Cow,
3 collections::{BTreeMap, HashSet},
4 fmt,
5};
6
7use jiff::Timestamp;
8use schemars::{JsonSchema, Schema, SchemaGenerator, json_schema};
9use serde::de::IntoDeserializer;
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use serde_path_to_error::Segment;
13
14use crate::*;
15
16fn producer_description_is_empty(description: &SafeDisplayText) -> bool {
17 description.as_str().is_empty()
18}
19
20#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
21pub struct SessionTitle(String);
22impl SessionTitle {
23 pub const MAX_BYTES: usize = 512;
24 pub fn new(value: impl Into<String>) -> Result<Self, EventSchemaError> {
25 let value = value.into();
26 if value.trim().is_empty() {
27 return Err(EventSchemaError::EmptyTitle);
28 }
29 if value.len() > Self::MAX_BYTES {
30 return Err(EventSchemaError::TitleTooLong);
31 }
32 if value.chars().any(char::is_control) {
33 return Err(EventSchemaError::TitleControlCharacter);
34 }
35 Ok(Self(value))
36 }
37 #[must_use]
38 pub fn as_str(&self) -> &str {
39 &self.0
40 }
41}
42impl fmt::Display for SessionTitle {
43 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
44 self.0.fmt(f)
45 }
46}
47impl Serialize for SessionTitle {
48 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
49 where
50 S: serde::Serializer,
51 {
52 s.serialize_str(&self.0)
53 }
54}
55impl<'de> Deserialize<'de> for SessionTitle {
56 fn deserialize<D>(d: D) -> Result<Self, D::Error>
57 where
58 D: serde::Deserializer<'de>,
59 {
60 Self::new(String::deserialize(d)?).map_err(serde::de::Error::custom)
61 }
62}
63impl JsonSchema for SessionTitle {
64 fn inline_schema() -> bool {
65 true
66 }
67 fn schema_name() -> Cow<'static, str> {
68 Cow::Borrowed("SessionTitle")
69 }
70 fn json_schema(_: &mut SchemaGenerator) -> Schema {
71 json_schema!({"type":"string","minLength":1,"maxLength":512,"description":"Nonblank control-free UTF-8 session title."})
72 }
73}
74
75#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
76pub struct EventOrigin(String);
77impl EventOrigin {
78 pub fn new(value: impl Into<String>) -> Result<Self, EventSchemaError> {
79 let value = value.into();
80 if value == "user" {
81 return Ok(Self(value));
82 }
83 let Some((class, slug)) = value.split_once(':') else {
84 return Err(EventSchemaError::InvalidOrigin);
85 };
86 if !matches!(class, "engine" | "plugin" | "client")
87 || slug.is_empty()
88 || slug.len() > 64
89 || !slug.bytes().enumerate().all(|(index, byte)| {
90 byte.is_ascii_lowercase() || byte.is_ascii_digit() || (index > 0 && byte == b'-')
91 })
92 {
93 return Err(EventSchemaError::InvalidOrigin);
94 }
95 Ok(Self(value))
96 }
97
98 #[must_use]
99 pub fn as_str(&self) -> &str {
100 &self.0
101 }
102
103 #[must_use]
104 pub fn plugin_name(&self) -> Option<&str> {
105 self.0.strip_prefix("plugin:")
106 }
107}
108impl fmt::Display for EventOrigin {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 self.0.fmt(f)
111 }
112}
113impl Serialize for EventOrigin {
114 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
115 where
116 S: serde::Serializer,
117 {
118 serializer.serialize_str(&self.0)
119 }
120}
121impl<'de> Deserialize<'de> for EventOrigin {
122 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
123 where
124 D: serde::Deserializer<'de>,
125 {
126 Self::new(String::deserialize(deserializer)?).map_err(serde::de::Error::custom)
127 }
128}
129impl JsonSchema for EventOrigin {
130 fn inline_schema() -> bool {
131 true
132 }
133 fn schema_name() -> Cow<'static, str> {
134 Cow::Borrowed("EventOrigin")
135 }
136 fn json_schema(_: &mut SchemaGenerator) -> Schema {
137 json_schema!({
138 "type":"string",
139 "pattern":"^(user|(engine|plugin|client):[a-z0-9][a-z0-9-]{0,63})$",
140 "description":"Validated event author origin."
141 })
142 }
143}
144
145#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
146#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
147pub enum SessionOrigin {
148 Root,
149 Delegated {
150 root_session_id: SessionId,
151 parent_session_id: SessionId,
152 parent_run_id: RunId,
153 parent_tool_call_id: ToolCallId,
154 invocation_id: InvocationId,
155 depth: u32,
156 },
157}
158
159#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, PartialEq, Serialize)]
160#[serde(rename_all = "snake_case")]
161pub enum SessionStatus {
162 Idle,
163 Running,
164 Completed,
165 Failed,
166 Cancelled,
167 Interrupted,
168}
169
170#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
171#[serde(deny_unknown_fields)]
172pub struct DelegationReservation {
173 pub invocation_id: InvocationId,
174 pub parent_session_id: SessionId,
175 pub parent_run_id: RunId,
176 pub parent_tool_call_id: ToolCallId,
177 pub child_session_id: SessionId,
178}
179
180#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
181#[serde(rename_all = "snake_case")]
182pub enum DelegatedContextRole {
183 User,
184 Assistant,
185}
186
187#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
188#[serde(deny_unknown_fields)]
189pub struct DelegatedContextTurn {
190 pub role: DelegatedContextRole,
191 pub text: String,
192}
193
194#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
195#[serde(rename_all = "snake_case")]
196pub enum StagedSkillProvenance {
197 SkillFork,
198}
199
200#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
201#[serde(deny_unknown_fields)]
202pub struct StagedSkillPayload {
203 pub provenance: StagedSkillProvenance,
204 pub name: String,
205 pub args: String,
206 pub rendered_body: String,
207 pub source_path: String,
208 pub base_dir: String,
209 #[schemars(length(max = 10))]
210 pub supporting_files: Vec<String>,
211 #[schemars(length(max = 256))]
212 pub grants: Vec<PermissionRule>,
213 #[serde(deserialize_with = "deserialize_required_option")]
214 #[schemars(with = "crate::NullableSchema<ModelKey>", required)]
215 pub model: Option<ModelKey>,
216}
217
218#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
219#[serde(deny_unknown_fields)]
220pub struct DelegateRequestPayload {
221 pub description: String,
222 pub prompt: String,
223 pub title: SessionTitle,
224 #[serde(deserialize_with = "deserialize_required_option")]
225 #[schemars(with = "crate::NullableSchema<SessionId>", required)]
226 pub resume_session_id: Option<SessionId>,
227 pub inherit_context: bool,
228 #[schemars(length(max = 65536))]
229 pub seeded_context: Vec<DelegatedContextTurn>,
230 pub background: bool,
231 #[serde(deserialize_with = "deserialize_required_option")]
232 #[schemars(with = "crate::NullableSchema<StagedSkillPayload>", required)]
233 pub staged_skill: Option<StagedSkillPayload>,
234}
235impl DelegateRequestPayload {
236 fn validate(&self) -> Result<(), EventSchemaError> {
237 if self.description.is_empty()
238 || self.prompt.is_empty()
239 || (self.resume_session_id.is_some() && self.inherit_context)
240 || (!self.inherit_context && !self.seeded_context.is_empty())
241 || self.seeded_context.len() > 65_536
242 || self.seeded_context.iter().any(|turn| turn.text.is_empty())
243 || self
244 .seeded_context
245 .iter()
246 .map(|turn| turn.text.len())
247 .sum::<usize>()
248 > 65_536
249 {
250 return Err(EventSchemaError::InvalidDelegationLifecycle);
251 }
252 if self.staged_skill.as_ref().is_some_and(|skill| {
253 skill.provenance != StagedSkillProvenance::SkillFork
254 || skill.name.is_empty()
255 || skill.rendered_body.is_empty()
256 || skill.source_path.is_empty()
257 || skill.base_dir.is_empty()
258 || skill.supporting_files.len() > 10
259 || skill.grants.len() > 256
260 || self.resume_session_id.is_some()
261 }) {
262 return Err(EventSchemaError::InvalidDelegationLifecycle);
263 }
264 Ok(())
265 }
266}
267
268#[derive(Clone, Debug, JsonSchema, PartialEq, Serialize)]
269pub struct SessionMeta {
270 pub session_id: SessionId,
271 pub origin: SessionOrigin,
272 #[serde(default, skip_serializing_if = "Option::is_none")]
274 pub short_id: Option<String>,
275 pub cwd_identity: CwdIdentity,
276 pub creation_selection: RunSelection,
277 pub runtime_revision: RuntimeRevision,
278 pub catalog_revision: CatalogRevision,
279 pub provider_state_revision: ProviderStateRevision,
280 pub model_revision: ModelRevision,
281 pub agent_revision: AgentRevision,
282 pub recipe_registry_revision: RecipeRegistryRevision,
283 pub manifest_revision: ModelSnapshotRevision,
284 #[serde(deserialize_with = "deserialize_required_option")]
285 #[schemars(with = "crate::NullableSchema<SessionTitle>", required)]
286 pub title: Option<SessionTitle>,
287 pub title_updated_seq: u64,
288 #[schemars(range(min = 1))]
289 pub last_event_seq: u64,
290 pub last_activity: Timestamp,
291 pub status: SessionStatus,
292 pub skipped_events: Vec<SkippedEvent>,
293}
294
295#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
296pub struct SkippedEvent {
297 pub seq: u64,
298 pub reason: String,
299}
300impl SessionMeta {
301 pub fn validate(&self) -> Result<(), EventSchemaError> {
302 if self.last_event_seq == 0 || self.title_updated_seq > self.last_event_seq {
303 return Err(EventSchemaError::InvalidSessionMetaSequence);
304 }
305 Ok(())
306 }
307}
308impl<'de> Deserialize<'de> for SessionMeta {
309 fn deserialize<D>(d: D) -> Result<Self, D::Error>
310 where
311 D: serde::Deserializer<'de>,
312 {
313 #[derive(Deserialize)]
314 struct Wire {
315 session_id: SessionId,
316 origin: SessionOrigin,
317 #[serde(default)]
318 short_id: Option<String>,
319 cwd_identity: CwdIdentity,
320 creation_selection: RunSelection,
321 runtime_revision: RuntimeRevision,
322 catalog_revision: CatalogRevision,
323 provider_state_revision: ProviderStateRevision,
324 model_revision: ModelRevision,
325 agent_revision: AgentRevision,
326 recipe_registry_revision: RecipeRegistryRevision,
327 manifest_revision: ModelSnapshotRevision,
328 #[serde(deserialize_with = "deserialize_required_option")]
329 title: Option<SessionTitle>,
330 title_updated_seq: u64,
331 last_event_seq: u64,
332 last_activity: Timestamp,
333 status: SessionStatus,
334 #[serde(default)]
335 skipped_events: Vec<SkippedEvent>,
336 }
337 let w = Wire::deserialize(d)?;
338 let value = Self {
339 session_id: w.session_id,
340 origin: w.origin,
341 short_id: w.short_id,
342 cwd_identity: w.cwd_identity,
343 creation_selection: w.creation_selection,
344 runtime_revision: w.runtime_revision,
345 catalog_revision: w.catalog_revision,
346 provider_state_revision: w.provider_state_revision,
347 model_revision: w.model_revision,
348 agent_revision: w.agent_revision,
349 recipe_registry_revision: w.recipe_registry_revision,
350 manifest_revision: w.manifest_revision,
351 title: w.title,
352 title_updated_seq: w.title_updated_seq,
353 last_event_seq: w.last_event_seq,
354 last_activity: w.last_activity,
355 status: w.status,
356 skipped_events: w.skipped_events,
357 };
358 value.validate().map_err(serde::de::Error::custom)?;
359 Ok(value)
360 }
361}
362
363#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
364#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
365pub enum SessionTitleChange {
366 UserSet {
367 title: SessionTitle,
368 client_rename_id: ClientRenameId,
369 },
370 UserClear {
371 client_rename_id: ClientRenameId,
372 },
373 UserReset {
374 client_rename_id: ClientRenameId,
375 },
376 InternalAgentSet {
377 title: SessionTitle,
378 invocation_id: InternalAgentInvocationId,
379 },
380 DelegatedSet {
381 title: SessionTitle,
382 invocation_id: InvocationId,
383 },
384 FallbackSet {
385 title: SessionTitle,
386 },
387}
388impl SessionTitleChange {
389 #[must_use]
390 pub fn user_rename_record(&self) -> Option<SessionRenameRecord> {
391 match self {
392 Self::UserSet {
393 title,
394 client_rename_id,
395 } => Some(SessionRenameRecord {
396 client_rename_id: client_rename_id.clone(),
397 change: SessionRenameChange::Set {
398 title: title.clone(),
399 },
400 }),
401 Self::UserClear { client_rename_id } => Some(SessionRenameRecord {
402 client_rename_id: client_rename_id.clone(),
403 change: SessionRenameChange::Clear,
404 }),
405 Self::UserReset { client_rename_id } => Some(SessionRenameRecord {
406 client_rename_id: client_rename_id.clone(),
407 change: SessionRenameChange::Reset,
408 }),
409 Self::InternalAgentSet { .. }
410 | Self::DelegatedSet { .. }
411 | Self::FallbackSet { .. } => None,
412 }
413 }
414}
415
416#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
417#[serde(deny_unknown_fields)]
418pub struct ArtifactReference {
419 pub uri: String,
420}
421impl ArtifactReference {
422 pub const MAX_URI_BYTES: usize = 1024;
423 pub fn validate(&self) -> Result<(), EventSchemaError> {
424 if self.uri.is_empty()
425 || self.uri.len() > Self::MAX_URI_BYTES
426 || self.uri.chars().any(char::is_control)
427 || !self.uri.starts_with("artifact://")
428 {
429 Err(EventSchemaError::InvalidArtifactReference)
430 } else {
431 Ok(())
432 }
433 }
434}
435
436#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
437#[serde(deny_unknown_fields)]
438pub struct ToolAttachment {
439 pub mime_type: MimeType,
440 #[serde(deserialize_with = "crate::deserialize_required_option")]
441 #[schemars(with = "crate::NullableSchema<String>", required)]
442 pub filename: Option<String>,
443 pub byte_length: u64,
444 pub sha256: Sha256Digest,
445 pub reference: ArtifactReference,
446}
447impl ToolAttachment {
448 pub fn validate(&self) -> Result<(), EventSchemaError> {
449 self.reference.validate()?;
450 if self.filename.as_ref().is_some_and(|name| {
451 name.is_empty() || name.len() > 512 || name.chars().any(char::is_control)
452 }) {
453 return Err(EventSchemaError::InvalidAttachmentFilename);
454 }
455 Ok(())
456 }
457}
458
459#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
460#[serde(rename_all = "snake_case")]
461pub enum ToolEmittedMessageRole {
462 System,
463 User,
464}
465
466#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
467#[serde(rename_all = "snake_case", deny_unknown_fields)]
468pub enum ToolEmittedContent {
469 Text(String),
470 File(ToolAttachment),
471}
472
473#[derive(Clone, Debug, JsonSchema, PartialEq, Serialize)]
474#[serde(deny_unknown_fields)]
475pub struct ToolEmittedMessage {
476 pub role: ToolEmittedMessageRole,
477 #[schemars(length(min = 1))]
478 pub content: Vec<ToolEmittedContent>,
479}
480impl ToolEmittedMessage {
481 pub const MAX_TEXT_BYTES: usize = 64 * 1024;
482
483 pub fn new(
484 role: ToolEmittedMessageRole,
485 content: Vec<ToolEmittedContent>,
486 ) -> Result<Self, EventSchemaError> {
487 let value = Self { role, content };
488 value.validate()?;
489 Ok(value)
490 }
491
492 pub fn validate(&self) -> Result<(), EventSchemaError> {
493 if self.content.is_empty() {
494 return Err(EventSchemaError::EmptyToolEmittedMessage);
495 }
496 let mut text_bytes = 0_usize;
497 for part in &self.content {
498 match part {
499 ToolEmittedContent::Text(text) => {
500 if text.trim().is_empty() {
501 return Err(EventSchemaError::EmptyToolEmittedText);
502 }
503 text_bytes = text_bytes
504 .checked_add(text.len())
505 .ok_or(EventSchemaError::ToolEmittedTextTooLarge)?;
506 if text_bytes > Self::MAX_TEXT_BYTES {
507 return Err(EventSchemaError::ToolEmittedTextTooLarge);
508 }
509 }
510 ToolEmittedContent::File(attachment) => attachment.validate()?,
511 }
512 }
513 Ok(())
514 }
515}
516impl<'de> Deserialize<'de> for ToolEmittedMessage {
517 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
518 where
519 D: serde::Deserializer<'de>,
520 {
521 #[derive(Deserialize)]
522 #[serde(deny_unknown_fields)]
523 struct Wire {
524 role: ToolEmittedMessageRole,
525 content: Vec<ToolEmittedContent>,
526 }
527
528 let wire = Wire::deserialize(deserializer)?;
529 Self::new(wire.role, wire.content).map_err(serde::de::Error::custom)
530 }
531}
532#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
533#[serde(deny_unknown_fields)]
534pub struct ToolOutputTruncation {
535 pub original_bytes: u64,
536 pub original_lines: u64,
537 pub retained: ArtifactReference,
538}
539
540#[derive(Clone, Debug, JsonSchema, PartialEq, Serialize)]
541#[serde(deny_unknown_fields)]
542pub struct PersistedToolResult {
543 pub title: SafeDisplayText,
544 pub output: String,
545 #[serde(default, skip_serializing_if = "Option::is_none")]
546 pub display: Option<String>,
547 #[serde(default, skip_serializing_if = "Option::is_none")]
548 pub retained_output: Option<crate::RetainedToolOutput>,
549 pub metadata: Value,
550 #[serde(deserialize_with = "crate::deserialize_required_option")]
551 #[schemars(with = "crate::NullableSchema<ToolOutputTruncation>", required)]
552 pub truncation: Option<ToolOutputTruncation>,
553 #[schemars(
554 description = "Direct result files. Together with files in additional_messages, at most 256 are allowed."
555 )]
556 #[schemars(length(max = 256))]
557 pub attachments: Vec<ToolAttachment>,
558 #[schemars(
559 description = "Emitted model-history messages. Their files share the 256-file limit with attachments."
560 )]
561 #[serde(default, skip_serializing_if = "Vec::is_empty")]
562 #[schemars(length(max = 4))]
563 pub additional_messages: Vec<ToolEmittedMessage>,
564}
565impl PersistedToolResult {
566 pub const MAX_OUTPUT_BYTES: usize = 2 * 1024 * 1024;
567 pub const MAX_METADATA_BYTES: usize = 2 * 1024 * 1024;
568 pub const MAX_ADDITIONAL_MESSAGES: usize = 4;
569 pub const MAX_ATTACHMENTS: usize = 256;
570
571 pub fn all_attachments(&self) -> impl Iterator<Item = &ToolAttachment> {
572 self.attachments.iter().chain(
573 self.additional_messages
574 .iter()
575 .flat_map(|message| message.content.iter())
576 .filter_map(|part| match part {
577 ToolEmittedContent::Text(_) => None,
578 ToolEmittedContent::File(attachment) => Some(attachment),
579 }),
580 )
581 }
582
583 pub fn validate(&self) -> Result<(), EventSchemaError> {
584 if self.output.len() > Self::MAX_OUTPUT_BYTES {
585 return Err(EventSchemaError::ToolOutputTooLarge);
586 }
587 if self.display.as_ref().is_some_and(|display| {
588 !crate::tool_output::validate_display(display, crate::MAX_TOOL_DISPLAY_BYTES)
589 }) {
590 return Err(EventSchemaError::ToolOutputTooLarge);
591 }
592 if let Some(retained) = &self.retained_output {
593 retained.reference.validate()?;
594 if retained.streams.is_empty() || retained.streams.len() > crate::MAX_TOOL_STREAMS {
595 return Err(EventSchemaError::ToolOutputTooLarge);
596 }
597 for stream in &retained.streams {
598 stream.reference.validate()?;
599 if let Some(name) = &stream.name {
600 crate::validate_tool_stream_name(name)
601 .map_err(|_| EventSchemaError::InvalidArtifactReference)?;
602 }
603 }
604 }
605 if serde_json::to_vec(&self.metadata)
606 .map_err(|_| EventSchemaError::InvalidJson)?
607 .len()
608 > Self::MAX_METADATA_BYTES
609 {
610 return Err(EventSchemaError::ToolMetadataTooLarge);
611 }
612 let emitted_attachment_count = self
613 .additional_messages
614 .iter()
615 .flat_map(|message| &message.content)
616 .filter(|part| matches!(part, ToolEmittedContent::File(_)))
617 .count();
618 if self
619 .attachments
620 .len()
621 .saturating_add(emitted_attachment_count)
622 > Self::MAX_ATTACHMENTS
623 {
624 return Err(EventSchemaError::TooManyAttachments);
625 }
626 if self.additional_messages.len() > Self::MAX_ADDITIONAL_MESSAGES {
627 return Err(EventSchemaError::TooManyToolEmittedMessages);
628 }
629 for message in &self.additional_messages {
630 message.validate()?;
631 }
632 for attachment in self.all_attachments() {
633 attachment.validate()?;
634 }
635 if let Some(truncation) = &self.truncation {
636 truncation.retained.validate()?;
637 }
638 Ok(())
639 }
640}
641impl<'de> Deserialize<'de> for PersistedToolResult {
642 fn deserialize<D>(d: D) -> Result<Self, D::Error>
643 where
644 D: serde::Deserializer<'de>,
645 {
646 #[derive(Deserialize)]
647 #[serde(deny_unknown_fields)]
648 struct Wire {
649 title: SafeDisplayText,
650 output: String,
651 #[serde(default)]
652 display: Option<String>,
653 #[serde(default)]
654 retained_output: Option<crate::RetainedToolOutput>,
655 metadata: Value,
656 #[serde(deserialize_with = "crate::deserialize_required_option")]
657 truncation: Option<ToolOutputTruncation>,
658 attachments: Vec<ToolAttachment>,
659 #[serde(default)]
660 additional_messages: Vec<ToolEmittedMessage>,
661 }
662 let w = Wire::deserialize(d)?;
663 let value = Self {
664 title: w.title,
665 output: w.output,
666 display: w.display,
667 retained_output: w.retained_output,
668 metadata: w.metadata,
669 truncation: w.truncation,
670 attachments: w.attachments,
671 additional_messages: w.additional_messages,
672 };
673 value.validate().map_err(serde::de::Error::custom)?;
674 Ok(value)
675 }
676}
677
678#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
679#[serde(deny_unknown_fields)]
680pub struct SafeToolError {
681 pub code: SafeCode,
682 pub message: SafeErrorMessage,
683}
684
685#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
686#[serde(deny_unknown_fields)]
687pub struct AssistantToolCallRef {
688 #[schemars(range(min = 1))]
689 pub model_turn_seq: u64,
690 pub content_index: u32,
691 pub model_call_id: ModelCallId,
692 #[serde(deserialize_with = "deserialize_required_option")]
693 #[schemars(with = "crate::NullableSchema<ProviderItemId>", required)]
694 pub provider_item_id: Option<ProviderItemId>,
695}
696
697#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
698#[serde(deny_unknown_fields)]
699pub struct ToolCallPresentation {
700 pub title: SafeDisplayText,
701 #[serde(deserialize_with = "deserialize_required_option")]
702 #[schemars(with = "crate::NullableSchema<BoundedDisplayText>", required)]
703 pub primary_argument: Option<BoundedDisplayText>,
704}
705
706#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
707#[serde(deny_unknown_fields)]
708pub struct ToolCallStart {
709 pub tool_call_id: ToolCallId,
710 #[serde(default)]
711 pub output: crate::ToolOutputDeclaration,
712 pub owner: AssistantToolCallRef,
713 pub presentation: ToolCallPresentation,
714 pub operation_fingerprint: OperationFingerprint,
715}
716
717#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
718#[serde(rename_all = "snake_case")]
719pub enum ToolTerminationOutcome {
720 Completed,
721 Failed,
722 Cancelled,
723 Interrupted,
724}
725
726#[derive(Clone, Debug, JsonSchema, PartialEq, Serialize)]
727#[serde(deny_unknown_fields)]
728pub struct ToolCallTermination {
729 pub tool_call_id: ToolCallId,
730 pub owner: AssistantToolCallRef,
731 pub outcome: ToolTerminationOutcome,
732 #[serde(deserialize_with = "crate::deserialize_required_option")]
733 #[schemars(with = "crate::NullableSchema<PersistedToolResult>", required)]
734 pub result: Option<PersistedToolResult>,
735 #[serde(deserialize_with = "crate::deserialize_required_option")]
736 #[schemars(with = "crate::NullableSchema<SafeToolError>", required)]
737 pub error: Option<SafeToolError>,
738}
739impl ToolCallTermination {
740 pub fn validate(&self) -> Result<(), EventSchemaError> {
741 if self.owner.model_turn_seq == 0 {
742 return Err(EventSchemaError::ZeroModelTurnSequence);
743 }
744 match self.outcome {
745 ToolTerminationOutcome::Completed if self.result.is_some() && self.error.is_none() => {}
746 ToolTerminationOutcome::Failed
747 | ToolTerminationOutcome::Cancelled
748 | ToolTerminationOutcome::Interrupted
749 if self.error.is_some() => {}
750 _ => return Err(EventSchemaError::InvalidToolTermination),
751 }
752 if let Some(result) = &self.result {
753 result.validate()?;
754 }
755 Ok(())
756 }
757 #[must_use]
758 pub fn matches_start(&self, start: &ToolCallStart) -> bool {
759 self.tool_call_id == start.tool_call_id && self.owner == start.owner
760 }
761}
762impl<'de> Deserialize<'de> for ToolCallTermination {
763 fn deserialize<D>(d: D) -> Result<Self, D::Error>
764 where
765 D: serde::Deserializer<'de>,
766 {
767 #[derive(Deserialize)]
768 #[serde(deny_unknown_fields)]
769 struct Wire {
770 tool_call_id: ToolCallId,
771 owner: AssistantToolCallRef,
772 outcome: ToolTerminationOutcome,
773 #[serde(deserialize_with = "crate::deserialize_required_option")]
774 result: Option<PersistedToolResult>,
775 #[serde(deserialize_with = "crate::deserialize_required_option")]
776 error: Option<SafeToolError>,
777 }
778 let w = Wire::deserialize(d)?;
779 let value = Self {
780 tool_call_id: w.tool_call_id,
781 owner: w.owner,
782 outcome: w.outcome,
783 result: w.result,
784 error: w.error,
785 };
786 value.validate().map_err(serde::de::Error::custom)?;
787 Ok(value)
788 }
789}
790
791#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
792#[serde(deny_unknown_fields)]
793pub struct Usage {
794 #[serde(deserialize_with = "crate::deserialize_required_option")]
795 #[schemars(with = "crate::NullableSchema<u64>", required)]
796 pub input_tokens: Option<u64>,
797 #[serde(deserialize_with = "crate::deserialize_required_option")]
798 #[schemars(with = "crate::NullableSchema<u64>", required)]
799 pub input_tokens_no_cache: Option<u64>,
800 #[serde(deserialize_with = "crate::deserialize_required_option")]
801 #[schemars(with = "crate::NullableSchema<u64>", required)]
802 pub input_tokens_cache_read: Option<u64>,
803 #[serde(deserialize_with = "crate::deserialize_required_option")]
804 #[schemars(with = "crate::NullableSchema<u64>", required)]
805 pub input_tokens_cache_write: Option<u64>,
806 #[serde(deserialize_with = "crate::deserialize_required_option")]
807 #[schemars(with = "crate::NullableSchema<u64>", required)]
808 pub output_tokens: Option<u64>,
809 #[serde(deserialize_with = "crate::deserialize_required_option")]
810 #[schemars(with = "crate::NullableSchema<u64>", required)]
811 pub output_tokens_text: Option<u64>,
812 #[serde(deserialize_with = "crate::deserialize_required_option")]
813 #[schemars(with = "crate::NullableSchema<u64>", required)]
814 pub output_tokens_reasoning: Option<u64>,
815}
816
817pub type SafeModelMetadata = BTreeMap<String, Value>;
818
819#[derive(Clone, Debug, Deserialize, Eq, Hash, JsonSchema, PartialEq, Serialize)]
820#[serde(deny_unknown_fields)]
821pub struct NativeContextScope {
822 pub provider_id: ProviderId,
823 pub model_id: ProviderModelId,
824 pub resource_id: SafeDisplayText,
825}
826
827#[derive(Clone, Debug, PartialEq)]
828pub enum NativeArtifactError {
829 PayloadTooLarge { size: usize, maximum: usize },
830 InvalidScope,
831 Serialization,
832}
833impl fmt::Display for NativeArtifactError {
834 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
835 match self {
836 Self::PayloadTooLarge { size, maximum } => write!(
837 f,
838 "native payload is {size} bytes, exceeding the {maximum}-byte limit"
839 ),
840 Self::InvalidScope => f.write_str("native artifact scope does not match its selection"),
841 Self::Serialization => f.write_str("native payload could not be serialized"),
842 }
843 }
844}
845impl std::error::Error for NativeArtifactError {}
846
847#[derive(Clone, JsonSchema, PartialEq, Serialize)]
848#[serde(deny_unknown_fields)]
849pub struct NativeReplayArtifact {
850 adapter_id: SafeCode,
851 selection_fingerprint: Sha256Digest,
852 scope: NativeContextScope,
853 #[schemars(extend("x-max-serialized-bytes"=2_097_152))]
854 payload: Value,
855}
856impl NativeReplayArtifact {
857 pub const MAX_PAYLOAD_BYTES: usize = 2 * 1024 * 1024;
858 pub fn new(
859 adapter_id: SafeCode,
860 selection_fingerprint: Sha256Digest,
861 scope: NativeContextScope,
862 payload: Value,
863 ) -> Result<Self, NativeArtifactError> {
864 validate_payload(&payload, Self::MAX_PAYLOAD_BYTES)?;
865 Ok(Self {
866 adapter_id,
867 selection_fingerprint,
868 scope,
869 payload,
870 })
871 }
872 #[must_use]
873 pub const fn selection_fingerprint(&self) -> &Sha256Digest {
874 &self.selection_fingerprint
875 }
876 #[must_use]
877 pub const fn adapter_id(&self) -> &SafeCode {
878 &self.adapter_id
879 }
880 #[must_use]
881 pub const fn scope(&self) -> &NativeContextScope {
882 &self.scope
883 }
884 #[must_use]
885 pub const fn payload(&self) -> &Value {
886 &self.payload
887 }
888
889 pub fn validate_for(
890 &self,
891 resolved: &ResolvedModelRef,
892 expected_scope: &NativeContextScope,
893 ) -> Result<(), NativeArtifactError> {
894 validate_native_identity(
895 &self.adapter_id,
896 &self.selection_fingerprint,
897 &self.scope,
898 resolved,
899 expected_scope,
900 )
901 }
902
903 pub fn validate_resolved(
904 &self,
905 resolved: &ResolvedModelRef,
906 ) -> Result<(), NativeArtifactError> {
907 self.validate_for(resolved, &self.scope)
908 }
909
910 pub fn validate_for_binding(
911 &self,
912 binding: &FrozenModelBinding,
913 expected_scope: &NativeContextScope,
914 ) -> Result<(), NativeArtifactError> {
915 binding
916 .validate()
917 .map_err(|_| NativeArtifactError::InvalidScope)?;
918 if self.adapter_id.as_str() != binding.descriptor.adapter_id.as_str()
919 || self.selection_fingerprint != binding.blueprint_fingerprint
920 || self.scope != *expected_scope
921 || self.scope.provider_id != binding.selection.model.provider_id()
922 || self.scope.model_id != binding.selection.model.model_id()
923 {
924 return Err(NativeArtifactError::InvalidScope);
925 }
926 Ok(())
927 }
928}
929impl fmt::Debug for NativeReplayArtifact {
930 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
931 f.debug_struct("NativeReplayArtifact")
932 .field("adapter_id", &self.adapter_id)
933 .field("selection_fingerprint", &self.selection_fingerprint)
934 .field("scope", &self.scope)
935 .field("payload", &"<redacted>")
936 .finish()
937 }
938}
939impl<'de> Deserialize<'de> for NativeReplayArtifact {
940 fn deserialize<D>(d: D) -> Result<Self, D::Error>
941 where
942 D: serde::Deserializer<'de>,
943 {
944 #[derive(Deserialize)]
945 #[serde(deny_unknown_fields)]
946 struct Wire {
947 adapter_id: SafeCode,
948 selection_fingerprint: Sha256Digest,
949 scope: NativeContextScope,
950 payload: Value,
951 }
952 let w = Wire::deserialize(d)?;
953 Self::new(w.adapter_id, w.selection_fingerprint, w.scope, w.payload)
954 .map_err(serde::de::Error::custom)
955 }
956}
957
958#[derive(Clone, Eq, JsonSchema, PartialEq, Serialize)]
959#[serde(deny_unknown_fields)]
960pub struct NativeContextWindow {
961 adapter_id: SafeCode,
962 selection_fingerprint: Sha256Digest,
963 scope: NativeContextScope,
964 #[schemars(extend("x-max-serialized-bytes"=33_554_432))]
965 payload: Value,
966}
967impl NativeContextWindow {
968 pub const MAX_PAYLOAD_BYTES: usize = 32 * 1024 * 1024;
969 pub fn new(
970 adapter_id: SafeCode,
971 selection_fingerprint: Sha256Digest,
972 scope: NativeContextScope,
973 payload: Value,
974 ) -> Result<Self, NativeArtifactError> {
975 validate_payload(&payload, Self::MAX_PAYLOAD_BYTES)?;
976 Ok(Self {
977 adapter_id,
978 selection_fingerprint,
979 scope,
980 payload,
981 })
982 }
983 #[must_use]
984 pub const fn selection_fingerprint(&self) -> &Sha256Digest {
985 &self.selection_fingerprint
986 }
987 #[must_use]
988 pub const fn adapter_id(&self) -> &SafeCode {
989 &self.adapter_id
990 }
991 #[must_use]
992 pub const fn scope(&self) -> &NativeContextScope {
993 &self.scope
994 }
995 #[must_use]
996 pub const fn payload(&self) -> &Value {
997 &self.payload
998 }
999
1000 pub fn validate_for(
1001 &self,
1002 resolved: &ResolvedModelRef,
1003 expected_scope: &NativeContextScope,
1004 ) -> Result<(), NativeArtifactError> {
1005 validate_native_identity(
1006 &self.adapter_id,
1007 &self.selection_fingerprint,
1008 &self.scope,
1009 resolved,
1010 expected_scope,
1011 )
1012 }
1013
1014 pub fn validate_for_binding(
1015 &self,
1016 binding: &FrozenModelBinding,
1017 expected_scope: &NativeContextScope,
1018 ) -> Result<(), NativeArtifactError> {
1019 binding
1020 .validate()
1021 .map_err(|_| NativeArtifactError::InvalidScope)?;
1022 if self.adapter_id.as_str() != binding.descriptor.adapter_id.as_str()
1023 || self.selection_fingerprint != binding.blueprint_fingerprint
1024 || self.scope != *expected_scope
1025 || self.scope.provider_id != binding.selection.model.provider_id()
1026 || self.scope.model_id != binding.selection.model.model_id()
1027 {
1028 return Err(NativeArtifactError::InvalidScope);
1029 }
1030 Ok(())
1031 }
1032}
1033impl fmt::Debug for NativeContextWindow {
1034 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1035 f.debug_struct("NativeContextWindow")
1036 .field("adapter_id", &self.adapter_id)
1037 .field("selection_fingerprint", &self.selection_fingerprint)
1038 .field("scope", &self.scope)
1039 .field("payload", &"<redacted>")
1040 .finish()
1041 }
1042}
1043impl<'de> Deserialize<'de> for NativeContextWindow {
1044 fn deserialize<D>(d: D) -> Result<Self, D::Error>
1045 where
1046 D: serde::Deserializer<'de>,
1047 {
1048 #[derive(Deserialize)]
1049 #[serde(deny_unknown_fields)]
1050 struct Wire {
1051 adapter_id: SafeCode,
1052 selection_fingerprint: Sha256Digest,
1053 scope: NativeContextScope,
1054 payload: Value,
1055 }
1056 let w = Wire::deserialize(d)?;
1057 Self::new(w.adapter_id, w.selection_fingerprint, w.scope, w.payload)
1058 .map_err(serde::de::Error::custom)
1059 }
1060}
1061fn validate_payload(payload: &Value, maximum: usize) -> Result<(), NativeArtifactError> {
1062 let size = serde_json::to_vec(payload)
1063 .map_err(|_| NativeArtifactError::Serialization)?
1064 .len();
1065 if size > maximum {
1066 Err(NativeArtifactError::PayloadTooLarge { size, maximum })
1067 } else {
1068 Ok(())
1069 }
1070}
1071
1072fn validate_native_identity(
1073 _adapter_id: &SafeCode,
1074 selection_fingerprint: &Sha256Digest,
1075 scope: &NativeContextScope,
1076 resolved: &ResolvedModelRef,
1077 expected_scope: &NativeContextScope,
1078) -> Result<(), NativeArtifactError> {
1079 resolved
1080 .validate()
1081 .map_err(|_| NativeArtifactError::InvalidScope)?;
1082 if selection_fingerprint != &resolved.selection_fingerprint
1083 || scope != expected_scope
1084 || scope.provider_id != resolved.provider_id
1085 || scope.model_id != resolved.model_id
1086 {
1087 return Err(NativeArtifactError::InvalidScope);
1088 }
1089 Ok(())
1090}
1091
1092#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1093#[serde(rename_all = "snake_case")]
1094pub enum ModelFinishReason {
1095 Stop,
1096 ToolCalls,
1097 Length,
1098 ContentFilter,
1099 Cancelled,
1100 Error,
1101 Aborted,
1102 Timeout,
1103 Refused,
1104 Unknown,
1105 Other(String),
1106}
1107#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1108#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1109pub enum PersistedFileSource {
1110 Artifact {
1111 byte_length: u64,
1112 sha256: Sha256Digest,
1113 reference: ArtifactReference,
1114 },
1115 Url {
1116 url: String,
1117 },
1118 ProviderReference {
1119 provider_id: ProviderId,
1120 id: SafeDisplayText,
1121 },
1122}
1123#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1124#[serde(deny_unknown_fields)]
1125pub struct PersistedFilePart {
1126 pub media_type: MimeType,
1127 #[serde(deserialize_with = "crate::deserialize_required_option")]
1128 #[schemars(with = "crate::NullableSchema<String>", required)]
1129 pub filename: Option<String>,
1130 pub source: PersistedFileSource,
1131 #[serde(deserialize_with = "crate::deserialize_required_option")]
1132 #[schemars(with = "crate::NullableSchema<SafeModelMetadata>", required)]
1133 pub metadata: Option<SafeModelMetadata>,
1134}
1135#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1136#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1137pub enum PersistedContentValue {
1138 Text { text: String },
1139 File { file: PersistedFilePart },
1140 Json { value: Value },
1141}
1142#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1143#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1144pub enum PersistedToolContent {
1145 Text {
1146 text: String,
1147 },
1148 Json {
1149 value: Value,
1150 },
1151 Mixed {
1152 values: Vec<PersistedContentValue>,
1153 },
1154 Denied {
1155 #[serde(deserialize_with = "deserialize_required_option")]
1156 #[schemars(with = "crate::NullableSchema<String>", required)]
1157 reason: Option<String>,
1158 },
1159}
1160#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1161#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1162pub enum PersistedAssistantPart {
1163 Text {
1164 text: String,
1165 #[serde(deserialize_with = "deserialize_required_option")]
1166 #[schemars(with = "crate::NullableSchema<SafeModelMetadata>", required)]
1167 metadata: Option<SafeModelMetadata>,
1168 },
1169 Reasoning {
1170 text: String,
1171 #[serde(deserialize_with = "deserialize_required_option")]
1172 #[schemars(with = "crate::NullableSchema<SafeModelMetadata>", required)]
1173 metadata: Option<SafeModelMetadata>,
1174 },
1175 ToolCall {
1176 id: ModelCallId,
1177 #[serde(deserialize_with = "deserialize_required_option")]
1178 #[schemars(with = "crate::NullableSchema<ProviderItemId>", required)]
1179 provider_item_id: Option<ProviderItemId>,
1180 name: SafeCode,
1181 input: Value,
1182 #[serde(deserialize_with = "deserialize_required_option")]
1183 #[schemars(with = "crate::NullableSchema<String>", required)]
1184 raw_input: Option<String>,
1185 #[serde(deserialize_with = "deserialize_required_option")]
1186 #[schemars(with = "crate::NullableSchema<SafeModelMetadata>", required)]
1187 metadata: Option<SafeModelMetadata>,
1188 },
1189 ToolResult {
1190 tool_call_id: ModelCallId,
1191 content: PersistedToolContent,
1192 is_error: bool,
1193 #[serde(deserialize_with = "deserialize_required_option")]
1194 #[schemars(with = "crate::NullableSchema<SafeModelMetadata>", required)]
1195 metadata: Option<SafeModelMetadata>,
1196 },
1197 File {
1198 file: PersistedFilePart,
1199 },
1200 Source {
1201 #[serde(deserialize_with = "deserialize_required_option")]
1202 #[schemars(with = "crate::NullableSchema<String>", required)]
1203 id: Option<String>,
1204 #[serde(deserialize_with = "deserialize_required_option")]
1205 #[schemars(with = "crate::NullableSchema<String>", required)]
1206 url: Option<String>,
1207 #[serde(deserialize_with = "deserialize_required_option")]
1208 #[schemars(with = "crate::NullableSchema<String>", required)]
1209 title: Option<String>,
1210 #[serde(deserialize_with = "deserialize_required_option")]
1211 #[schemars(with = "crate::NullableSchema<String>", required)]
1212 media_type: Option<String>,
1213 #[serde(deserialize_with = "deserialize_required_option")]
1214 #[schemars(with = "crate::NullableSchema<String>", required)]
1215 excerpt: Option<String>,
1216 #[serde(deserialize_with = "deserialize_required_option")]
1217 #[schemars(with = "crate::NullableSchema<SafeModelMetadata>", required)]
1218 metadata: Option<SafeModelMetadata>,
1219 },
1220 ToolApproval {
1221 tool_call_id: ModelCallId,
1222 #[serde(deserialize_with = "deserialize_required_option")]
1223 #[schemars(with = "crate::NullableSchema<String>", required)]
1224 message: Option<String>,
1225 #[serde(deserialize_with = "deserialize_required_option")]
1226 #[schemars(with = "crate::NullableSchema<SafeModelMetadata>", required)]
1227 metadata: Option<SafeModelMetadata>,
1228 },
1229 Custom {
1230 kind: SafeCode,
1231 data: Value,
1232 #[serde(deserialize_with = "deserialize_required_option")]
1233 #[schemars(with = "crate::NullableSchema<SafeModelMetadata>", required)]
1234 metadata: Option<SafeModelMetadata>,
1235 },
1236}
1237
1238#[derive(Clone, Debug, JsonSchema, PartialEq, Serialize)]
1239#[serde(deny_unknown_fields)]
1240pub struct PersistedModelTurn {
1241 #[schemars(length(max = 4096))]
1242 pub content: Vec<PersistedAssistantPart>,
1243 pub provider_options: BTreeMap<String, Value>,
1244 pub finish_reason: ModelFinishReason,
1245 pub usage: Usage,
1246 pub response_metadata: SafeModelMetadata,
1247 pub provider_metadata: SafeModelMetadata,
1248 #[serde(deserialize_with = "crate::deserialize_required_option")]
1249 #[schemars(with = "crate::NullableSchema<NativeReplayArtifact>", required)]
1250 pub native_replay: Option<NativeReplayArtifact>,
1251}
1252impl PersistedModelTurn {
1253 pub const MAX_SERIALIZED_BYTES: usize = 8 * 1024 * 1024;
1254 pub fn validate(&self) -> Result<(), EventSchemaError> {
1255 if self.content.len() > 4096 {
1256 return Err(EventSchemaError::TooManyModelParts);
1257 }
1258 if serde_json::to_vec(self)
1259 .map_err(|_| EventSchemaError::InvalidJson)?
1260 .len()
1261 > Self::MAX_SERIALIZED_BYTES
1262 {
1263 return Err(EventSchemaError::ModelTurnTooLarge);
1264 }
1265 Ok(())
1266 }
1267
1268 pub fn validate_for(&self, resolved: &ResolvedModelRef) -> Result<(), EventSchemaError> {
1269 self.validate()?;
1270 if let Some(native_replay) = &self.native_replay {
1271 native_replay
1272 .validate_resolved(resolved)
1273 .map_err(|_| EventSchemaError::NativeSelectionMismatch)?;
1274 }
1275 Ok(())
1276 }
1277}
1278impl<'de> Deserialize<'de> for PersistedModelTurn {
1279 fn deserialize<D>(d: D) -> Result<Self, D::Error>
1280 where
1281 D: serde::Deserializer<'de>,
1282 {
1283 #[derive(Deserialize)]
1284 #[serde(deny_unknown_fields)]
1285 struct Wire {
1286 content: Vec<PersistedAssistantPart>,
1287 provider_options: BTreeMap<String, Value>,
1288 finish_reason: ModelFinishReason,
1289 usage: Usage,
1290 response_metadata: SafeModelMetadata,
1291 provider_metadata: SafeModelMetadata,
1292 #[serde(deserialize_with = "crate::deserialize_required_option")]
1293 native_replay: Option<NativeReplayArtifact>,
1294 }
1295 let w = Wire::deserialize(d)?;
1296 let value = Self {
1297 content: w.content,
1298 provider_options: w.provider_options,
1299 finish_reason: w.finish_reason,
1300 usage: w.usage,
1301 response_metadata: w.response_metadata,
1302 provider_metadata: w.provider_metadata,
1303 native_replay: w.native_replay,
1304 };
1305 value.validate().map_err(serde::de::Error::custom)?;
1306 Ok(value)
1307 }
1308}
1309
1310#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1311#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1312pub enum ReplayDisposition {
1313 Replayed,
1314 NoArtifact,
1315 DiscardedForeignAdapter {
1316 found: SafeCode,
1317 expected: SafeCode,
1318 },
1319 DiscardedForeignModelSelection {
1320 #[serde(deserialize_with = "crate::deserialize_required_model_selection")]
1321 #[schemars(with = "crate::RequiredModelSelectionSchema")]
1322 found: ModelSelection,
1323 #[serde(deserialize_with = "crate::deserialize_required_model_selection")]
1324 #[schemars(with = "crate::RequiredModelSelectionSchema")]
1325 expected: ModelSelection,
1326 },
1327 DiscardedForeignVariant {
1328 #[serde(deserialize_with = "deserialize_required_option")]
1329 #[schemars(with = "crate::NullableSchema<VariantId>", required)]
1330 found: Option<VariantId>,
1331 #[serde(deserialize_with = "deserialize_required_option")]
1332 #[schemars(with = "crate::NullableSchema<VariantId>", required)]
1333 expected: Option<VariantId>,
1334 },
1335 DiscardedInvalidPayload {
1336 reason: SafeErrorMessage,
1337 },
1338 ReconstructedNormalizedHistory,
1339}
1340#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1341#[serde(deny_unknown_fields)]
1342pub struct ReplayDecision {
1343 pub history_index: u64,
1344 pub disposition: ReplayDisposition,
1345}
1346
1347#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1348#[serde(rename_all = "snake_case")]
1349pub enum ModelErrorStage {
1350 #[default]
1351 Unknown,
1352 RequestValidation,
1353 RequestEncoding,
1354 Connect,
1355 ResponseHeaders,
1356 ResponseBody,
1357 StreamRead,
1358 StreamDecode,
1359 StreamEvent,
1360 StreamFinalize,
1361 ReplayEncode,
1362 ReplayDecode,
1363 NativeContextEncode,
1364 NativeContextDecode,
1365 Middleware,
1366}
1367#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, PartialEq, Serialize)]
1368#[serde(rename_all = "snake_case")]
1369pub enum ModelErrorKind {
1370 Transport,
1371 Timeout,
1372 RateLimited,
1373 Auth,
1374 PermissionDenied,
1375 InvalidRequest,
1376 ModelNotFound,
1377 ContextLength,
1378 Quota,
1379 Overload,
1380 Unsupported,
1381 UnexpectedEof,
1382 InvalidResponse,
1383 InvalidToolInput,
1384 ContentFilter,
1385 Replay,
1386 NativeContext,
1387 Provider,
1388 Abort,
1389 Unknown,
1390}
1391#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1392#[serde(deny_unknown_fields)]
1393pub struct ModelErrorSummary {
1394 #[serde(default, skip_serializing_if = "Option::is_none")]
1395 pub response_body: Option<DiagnosticText>,
1396 pub kind: ModelErrorKind,
1397 pub message: SafeErrorMessage,
1398 pub retryable: bool,
1399 pub stage: ModelErrorStage,
1400 #[serde(deserialize_with = "crate::deserialize_required_option")]
1401 #[schemars(with = "crate::NullableSchema<u16>", required)]
1402 pub http_status: Option<u16>,
1403 pub bytes_received: u64,
1404 #[serde(deserialize_with = "crate::deserialize_required_option")]
1405 #[schemars(with = "crate::NullableSchema<SafeCode>", required)]
1406 pub vendor_code: Option<SafeCode>,
1407 #[serde(deserialize_with = "crate::deserialize_required_option")]
1408 #[schemars(with = "crate::NullableSchema<SafeDisplayText>", required)]
1409 pub request_id: Option<SafeDisplayText>,
1410 #[serde(deserialize_with = "crate::deserialize_required_option")]
1411 #[schemars(with = "crate::NullableSchema<u64>", required)]
1412 pub retry_after_ms: Option<u64>,
1413}
1414
1415#[derive(
1416 Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, Ord, PartialEq, PartialOrd, Serialize,
1417)]
1418#[serde(rename_all = "snake_case")]
1419pub enum InternalAgentKind {
1420 Approval,
1421 ContextCompaction,
1422 SessionTitle,
1423}
1424
1425#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1426#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1427pub enum FrozenInternalAgentFallback {
1428 ParentModel,
1429 Model { binding: Box<FrozenModelBinding> },
1430}
1431
1432#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1433#[serde(deny_unknown_fields)]
1434pub struct FrozenInternalAgentDefinition {
1435 pub kind: InternalAgentKind,
1436 pub agent: AgentId,
1437 #[schemars(length(min = 1, max = 512))]
1438 pub description: String,
1439 pub document_source: AgentDocumentSource,
1440 pub document_fingerprint: Sha256Digest,
1441 #[schemars(length(min = 1, max = 131_072))]
1442 pub composed_prompt: String,
1443 pub prompt_fingerprint: Sha256Digest,
1444 pub enabled: bool,
1445 pub max_output_tokens: u64,
1446 pub timeout_ms: u64,
1447 #[schemars(length(max = 256))]
1448 pub fallbacks: Vec<FrozenInternalAgentFallback>,
1449}
1450
1451impl FrozenInternalAgentDefinition {
1452 pub fn validate(&self) -> Result<(), &'static str> {
1453 if self.description.trim().is_empty()
1454 || self.description.len() > 512
1455 || self.description.chars().any(char::is_control)
1456 || self.composed_prompt.trim().is_empty()
1457 || self.composed_prompt.len() > AgentSnapshot::MAX_PROMPT_BYTES
1458 || self.fallbacks.len() > 256
1459 {
1460 return Err("invalid frozen internal agent definition");
1461 }
1462 for fallback in &self.fallbacks {
1463 if let FrozenInternalAgentFallback::Model { binding } = fallback {
1464 binding
1465 .validate()
1466 .map_err(|_| "invalid frozen internal agent binding")?;
1467 }
1468 }
1469 Ok(())
1470 }
1471}
1472#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1473#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1474pub enum InternalAgentBackend {
1475 Model {
1476 resolved_model: ResolvedModelRef,
1477 },
1478 Builtin {
1479 name: SafeCode,
1480 revision: SafeDisplayText,
1481 },
1482}
1483#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1484#[serde(deny_unknown_fields)]
1485pub struct SafeInternalAgentCall {
1486 pub name: SafeCode,
1487 pub input_summary: SafeDisplayText,
1488 pub input_digest: Sha256Digest,
1489}
1490#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1491#[serde(deny_unknown_fields)]
1492pub struct SafeInternalAgentResult {
1493 pub output_summary: SafeDisplayText,
1494 pub output_digest: Sha256Digest,
1495}
1496#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1497#[serde(deny_unknown_fields)]
1498pub struct InternalAgentFailure {
1499 pub code: SafeCode,
1500 pub message: SafeErrorMessage,
1501 pub retryable: bool,
1502 #[serde(deserialize_with = "crate::deserialize_required_option")]
1503 #[schemars(with = "crate::NullableSchema<ModelErrorSummary>", required)]
1504 pub model_error: Option<ModelErrorSummary>,
1505}
1506
1507fn validate_internal_backend(backend: &InternalAgentBackend) -> Result<(), EventSchemaError> {
1508 match backend {
1509 InternalAgentBackend::Model { resolved_model } => resolved_model
1510 .validate()
1511 .map_err(|_| EventSchemaError::InvalidInternalAgentLifecycle),
1512 InternalAgentBackend::Builtin { .. } => Ok(()),
1513 }
1514}
1515
1516fn validate_internal_failure(failure: &InternalAgentFailure) -> Result<(), EventSchemaError> {
1517 if failure
1518 .model_error
1519 .as_ref()
1520 .is_some_and(|error| error.retryable != failure.retryable)
1521 {
1522 Err(EventSchemaError::InvalidInternalAgentLifecycle)
1523 } else {
1524 Ok(())
1525 }
1526}
1527
1528#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1529#[serde(deny_unknown_fields)]
1530pub struct ContextCheckpointBoundaries {
1531 #[schemars(range(min = 1))]
1532 pub source_from_seq: u64,
1533 #[schemars(range(min = 1))]
1534 pub source_through_seq: u64,
1535 #[serde(default)]
1536 pub recent_from_seq: Option<u64>,
1537 pub input_through_seq: u64,
1538 #[serde(deserialize_with = "crate::deserialize_required_option")]
1539 #[schemars(with = "crate::NullableSchema<u64>", required)]
1540 pub prior_checkpoint_seq: Option<u64>,
1541}
1542#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
1543pub struct SummaryByteLimit(u64);
1544impl SummaryByteLimit {
1545 pub const GLOBAL_MAX_BYTES: u64 = 2 * 1024 * 1024;
1546 pub fn new(value: u64) -> Result<Self, EventSchemaError> {
1547 if value > Self::GLOBAL_MAX_BYTES {
1548 Err(EventSchemaError::SummaryLimitTooLarge)
1549 } else {
1550 Ok(Self(value))
1551 }
1552 }
1553 #[must_use]
1554 pub const fn get(self) -> u64 {
1555 self.0
1556 }
1557}
1558impl Serialize for SummaryByteLimit {
1559 fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
1560 where
1561 S: serde::Serializer,
1562 {
1563 s.serialize_u64(self.0)
1564 }
1565}
1566impl<'de> Deserialize<'de> for SummaryByteLimit {
1567 fn deserialize<D>(d: D) -> Result<Self, D::Error>
1568 where
1569 D: serde::Deserializer<'de>,
1570 {
1571 Self::new(u64::deserialize(d)?).map_err(serde::de::Error::custom)
1572 }
1573}
1574impl JsonSchema for SummaryByteLimit {
1575 fn inline_schema() -> bool {
1576 true
1577 }
1578 fn schema_name() -> Cow<'static, str> {
1579 Cow::Borrowed("SummaryByteLimit")
1580 }
1581 fn json_schema(_: &mut SchemaGenerator) -> Schema {
1582 json_schema!({"type":"integer","minimum":0,"maximum":2_097_152})
1583 }
1584}
1585#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1586#[serde(deny_unknown_fields)]
1587pub struct ContextCheckpointBudgets {
1588 #[schemars(range(min = 1))]
1589 pub context_limit_tokens: u64,
1590 #[schemars(range(min = 1))]
1591 pub trigger_tokens: u64,
1592 #[schemars(range(min = 1))]
1593 pub input_tokens_before: u64,
1594 pub input_tokens_after: u64,
1595 #[serde(default)]
1596 pub keep_recent_tokens: u64,
1597 pub max_summary_bytes: SummaryByteLimit,
1598}
1599
1600#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1601#[serde(deny_unknown_fields)]
1602pub struct ContextRehydratedFile {
1603 pub path: SafeDisplayText,
1604 pub content: String,
1605 pub byte_length: u64,
1606 pub sha256: Sha256Digest,
1607}
1608
1609#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1610#[serde(deny_unknown_fields)]
1611pub struct AgentMdEntry {
1612 pub source: SafeDisplayText,
1613 pub content: String,
1614 pub byte_length: u64,
1615}
1616impl AgentMdEntry {
1617 pub const MAX_CONTENT_BYTES: usize = 2 * 1024 * 1024;
1618
1619 pub fn validate(&self) -> Result<(), EventSchemaError> {
1620 if self.content.len() > Self::MAX_CONTENT_BYTES
1621 || self.byte_length != self.content.len() as u64
1622 {
1623 return Err(EventSchemaError::InvalidAgentMd);
1624 }
1625 Ok(())
1626 }
1627}
1628
1629#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1630#[serde(deny_unknown_fields)]
1631pub struct AgentMdSkipped {
1632 pub path: SafeDisplayText,
1633 pub byte_length: u64,
1634}
1635impl AgentMdSkipped {
1636 pub const MAX_SKIP_BYTES: u64 = 2 * 1024 * 1024;
1637
1638 pub fn validate(&self) -> Result<(), EventSchemaError> {
1639 if self.byte_length <= Self::MAX_SKIP_BYTES {
1640 return Err(EventSchemaError::InvalidAgentMd);
1641 }
1642 Ok(())
1643 }
1644}
1645
1646impl ContextRehydratedFile {
1647 pub const MAX_CONTENT_BYTES: usize = 32 * 1024;
1648
1649 pub fn validate(&self) -> Result<(), EventSchemaError> {
1650 if self.content.len() > Self::MAX_CONTENT_BYTES
1651 || self.byte_length != self.content.len() as u64
1652 || self.sha256 != Sha256Digest::of_bytes(self.content.as_bytes())
1653 {
1654 return Err(EventSchemaError::InvalidContextRehydration);
1655 }
1656 Ok(())
1657 }
1658}
1659
1660#[derive(Clone, Debug, Eq, JsonSchema, PartialEq, Serialize)]
1661#[serde(deny_unknown_fields)]
1662pub struct InternalSummaryCheckpoint {
1663 summary: String,
1664 byte_length: u64,
1665 sha256: Sha256Digest,
1666 invocation_id: InternalAgentInvocationId,
1667 internal_run_id: InternalAgentRunId,
1668}
1669impl InternalSummaryCheckpoint {
1670 pub fn new(
1671 summary: String,
1672 invocation_id: InternalAgentInvocationId,
1673 internal_run_id: InternalAgentRunId,
1674 max: SummaryByteLimit,
1675 ) -> Result<Self, EventSchemaError> {
1676 let byte_length = summary.len() as u64;
1677 if byte_length > max.get() || byte_length > SummaryByteLimit::GLOBAL_MAX_BYTES {
1678 return Err(EventSchemaError::SummaryTooLarge);
1679 }
1680 let sha256 = Sha256Digest::of_bytes(summary.as_bytes());
1681 Ok(Self {
1682 summary,
1683 byte_length,
1684 sha256,
1685 invocation_id,
1686 internal_run_id,
1687 })
1688 }
1689 #[must_use]
1690 pub fn summary(&self) -> &str {
1691 &self.summary
1692 }
1693 #[must_use]
1694 pub const fn byte_length(&self) -> u64 {
1695 self.byte_length
1696 }
1697}
1698impl<'de> Deserialize<'de> for InternalSummaryCheckpoint {
1699 fn deserialize<D>(d: D) -> Result<Self, D::Error>
1700 where
1701 D: serde::Deserializer<'de>,
1702 {
1703 #[derive(Deserialize)]
1704 #[serde(deny_unknown_fields)]
1705 struct Wire {
1706 summary: String,
1707 byte_length: u64,
1708 sha256: Sha256Digest,
1709 invocation_id: InternalAgentInvocationId,
1710 internal_run_id: InternalAgentRunId,
1711 }
1712 let w = Wire::deserialize(d)?;
1713 if w.summary.len() as u64 != w.byte_length
1714 || Sha256Digest::of_bytes(w.summary.as_bytes()) != w.sha256
1715 || w.byte_length > SummaryByteLimit::GLOBAL_MAX_BYTES
1716 {
1717 return Err(serde::de::Error::custom(
1718 "invalid summary length, digest, or bound",
1719 ));
1720 }
1721 Ok(Self {
1722 summary: w.summary,
1723 byte_length: w.byte_length,
1724 sha256: w.sha256,
1725 invocation_id: w.invocation_id,
1726 internal_run_id: w.internal_run_id,
1727 })
1728 }
1729}
1730
1731#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1732#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1733pub enum ContextCheckpoint {
1734 InternalSummary {
1735 #[serde(flatten)]
1736 checkpoint: InternalSummaryCheckpoint,
1737 },
1738 NativeWindow {
1739 window: NativeContextWindow,
1740 },
1741}
1742#[derive(Clone, Debug, Eq, JsonSchema, PartialEq, Serialize)]
1743#[serde(deny_unknown_fields)]
1744pub struct ContextCheckpointCommit {
1745 pub checkpoint: ContextCheckpoint,
1746 pub boundaries: ContextCheckpointBoundaries,
1747 pub budgets: ContextCheckpointBudgets,
1748}
1749impl ContextCheckpointCommit {
1750 pub fn validate(&self) -> Result<(), EventSchemaError> {
1751 let recent_outside_source =
1752 self.boundaries
1753 .recent_from_seq
1754 .is_some_and(|recent_from_seq| {
1755 recent_from_seq < self.boundaries.source_from_seq
1756 || recent_from_seq > self.boundaries.source_through_seq
1757 });
1758 let native_has_recent_tail =
1759 matches!(self.checkpoint, ContextCheckpoint::NativeWindow { .. })
1760 && (self.boundaries.recent_from_seq.is_some()
1761 || self.budgets.keep_recent_tokens != 0);
1762 if self.boundaries.source_from_seq == 0
1763 || self.boundaries.source_from_seq > self.boundaries.source_through_seq
1764 || recent_outside_source
1765 || self.boundaries.input_through_seq < self.boundaries.source_through_seq
1766 || self.budgets.context_limit_tokens == 0
1767 || self.budgets.trigger_tokens > self.budgets.context_limit_tokens
1768 || self.budgets.input_tokens_after >= self.budgets.input_tokens_before
1769 || native_has_recent_tail
1770 {
1771 return Err(EventSchemaError::InvalidCheckpointBoundaries);
1772 }
1773 match &self.checkpoint {
1774 ContextCheckpoint::InternalSummary { checkpoint }
1775 if checkpoint.byte_length() > self.budgets.max_summary_bytes.get() =>
1776 {
1777 Err(EventSchemaError::SummaryTooLarge)
1778 }
1779 _ => Ok(()),
1780 }
1781 }
1782
1783 pub fn validate_for_binding(
1784 &self,
1785 binding: &FrozenModelBinding,
1786 ) -> Result<(), EventSchemaError> {
1787 self.validate()?;
1788 if let ContextCheckpoint::NativeWindow { window } = &self.checkpoint {
1789 window
1790 .validate_for_binding(binding, window.scope())
1791 .map_err(|_| EventSchemaError::NativeSelectionMismatch)?;
1792 }
1793 Ok(())
1794 }
1795}
1796impl<'de> Deserialize<'de> for ContextCheckpointCommit {
1797 fn deserialize<D>(d: D) -> Result<Self, D::Error>
1798 where
1799 D: serde::Deserializer<'de>,
1800 {
1801 #[derive(Deserialize)]
1802 #[serde(deny_unknown_fields)]
1803 struct Wire {
1804 checkpoint: ContextCheckpoint,
1805 boundaries: ContextCheckpointBoundaries,
1806 budgets: ContextCheckpointBudgets,
1807 }
1808 let w = Wire::deserialize(d)?;
1809 let value = Self {
1810 checkpoint: w.checkpoint,
1811 boundaries: w.boundaries,
1812 budgets: w.budgets,
1813 };
1814 value.validate().map_err(serde::de::Error::custom)?;
1815 Ok(value)
1816 }
1817}
1818
1819#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1820#[serde(tag = "type", rename_all = "snake_case")]
1821#[allow(clippy::large_enum_variant)]
1822pub enum EventPayload {
1823 SessionCreated {
1824 origin: SessionOrigin,
1825 #[serde(default, skip_serializing_if = "Option::is_none")]
1829 short_id: Option<String>,
1830 cwd_identity: CwdIdentity,
1831 creation_selection: RunSelection,
1832 creation_agent: Box<AgentSnapshot>,
1833 runtime_revision: RuntimeRevision,
1834 catalog_revision: CatalogRevision,
1835 provider_state_revision: ProviderStateRevision,
1836 model_revision: ModelRevision,
1837 agent_revision: AgentRevision,
1838 recipe_registry_revision: RecipeRegistryRevision,
1839 manifest_revision: ModelSnapshotRevision,
1840 },
1841 SessionReverted {
1842 #[schemars(range(min = 1))]
1843 through_seq: u64,
1844 },
1845 SessionPermissionOverlaySet {
1846 overlay: SessionPermissionOverlay,
1847 },
1848 SkillLoaded {
1849 name: String,
1850 rendered_body: String,
1851 source_path: String,
1852 args: String,
1853 base_dir: String,
1854 #[schemars(length(max = 10))]
1855 supporting_files: Vec<String>,
1856 },
1857 SkillInvocationNoted {
1858 name: String,
1859 },
1860 AgentMdLoaded {
1861 #[schemars(length(min = 1, max = 2))]
1862 entries: Vec<AgentMdEntry>,
1863 },
1864 AgentMdSkipped {
1865 path: SafeDisplayText,
1866 byte_length: u64,
1867 },
1868 PluginEventAdded {
1869 plugin: String,
1870 name: String,
1871 payload: Value,
1872 },
1873 PluginDiagnostic {
1874 plugin: String,
1875 kind: PluginDiagnosticKind,
1876 message: String,
1877 count: u64,
1878 },
1879 GoalActivated {
1880 goal_id: GoalId,
1881 objective: String,
1882 revision: u64,
1883 #[serde(default, skip_serializing_if = "Option::is_none")]
1884 selection: Option<RunSelection>,
1885 },
1886 GoalChecklistRevised {
1887 goal_id: GoalId,
1888 items: Vec<GoalItem>,
1889 revision: u64,
1890 },
1891 GoalLifecycleChanged {
1892 goal_id: GoalId,
1893 status: GoalStatus,
1894 revision: u64,
1895 #[serde(default, skip_serializing_if = "Option::is_none")]
1896 selection: Option<RunSelection>,
1897 },
1898 ProducerMessageAccepted {
1901 message_id: ProducerMessageId,
1902 producer_owner: ProducerOwner,
1903 mode: ProducerDeliveryMode,
1904 idempotency_key: ProducerIdempotencyKey,
1905 #[serde(default, skip_serializing_if = "producer_description_is_empty")]
1906 description: SafeDisplayText,
1907 body: String,
1908 #[serde(default, skip_serializing_if = "Option::is_none")]
1909 reminder: Option<GoalReminderIdentity>,
1910 #[serde(default, skip_serializing_if = "Option::is_none")]
1916 agent_hop: Option<u32>,
1917 },
1918 ProducerMessageAdmitted {
1921 message_id: ProducerMessageId,
1922 },
1923 ProducerMessagesClaimed {
1926 #[schemars(length(min = 1))]
1927 message_ids: Vec<ProducerMessageId>,
1928 },
1929 ProducerMessagesReleased {
1931 #[schemars(range(min = 1))]
1932 claim_seq: u64,
1933 },
1934 ProducerMessageConsumed {
1937 message_id: ProducerMessageId,
1938 run_id: RunId,
1939 },
1940 ProducerMessageDiscarded {
1943 message_id: ProducerMessageId,
1944 #[serde(default, skip_serializing_if = "Option::is_none")]
1945 reminder: Option<GoalReminderIdentity>,
1946 #[serde(default, skip_serializing_if = "Option::is_none")]
1947 producer_owner: Option<ProducerOwner>,
1948 },
1949 RunStarted {
1950 client_run_id: ClientRunId,
1951 selection: RunSelection,
1952 agent: Box<AgentSnapshot>,
1953 runtime_revision: RuntimeRevision,
1954 catalog_revision: CatalogRevision,
1955 provider_state_revision: ProviderStateRevision,
1956 model_revision: ModelRevision,
1957 agent_revision: AgentRevision,
1958 recipe_registry_revision: RecipeRegistryRevision,
1959 manifest_revision: ModelSnapshotRevision,
1960 #[schemars(length(min = 1, max = 256))]
1961 selected_suffix: Vec<FrozenModelBinding>,
1962 #[serde(default)]
1963 #[schemars(length(max = 3))]
1964 internal_agents: Vec<FrozenInternalAgentDefinition>,
1965 input_through_seq: u64,
1966 },
1967 MessageInjected {
1968 role: crate::ExtensionMessageRole,
1969 input: String,
1970 },
1971 UserInputAdmitted {
1972 input: String,
1973 },
1974 UserInputSubmitted {
1975 input: String,
1976 },
1977 UserInputTransformed {
1978 original_input: String,
1979 input: String,
1980 },
1981 UserInputRecalled {
1982 input: String,
1983 },
1984 UserInputRecalledV2 {
1985 user_input_seq: u64,
1986 input: String,
1987 },
1988 UserInputApplied {
1989 user_input_seq: u64,
1990 },
1991 DelegatedContextSeeded {
1992 invocation_id: InvocationId,
1993 #[schemars(length(max = 65536))]
1994 turns: Vec<DelegatedContextTurn>,
1995 },
1996 RunCompleted {
1997 #[serde(deserialize_with = "deserialize_required_option")]
1998 #[schemars(with = "crate::NullableSchema<String>", required)]
1999 final_text: Option<String>,
2000 },
2001 RunFailed {
2002 error: SafeErrorMessage,
2003 #[serde(default, skip_serializing_if = "Option::is_none")]
2004 model_error: Option<ModelErrorSummary>,
2005 #[serde(default, skip_serializing_if = "Option::is_none")]
2006 resolved_model: Option<ResolvedModelRef>,
2007 },
2008 RunCancelled {
2009 #[serde(deserialize_with = "deserialize_required_option")]
2010 #[schemars(with = "crate::NullableSchema<SafeErrorMessage>", required)]
2011 reason: Option<SafeErrorMessage>,
2012 },
2013 RunInterrupted {
2014 #[serde(deserialize_with = "deserialize_required_option")]
2015 #[schemars(with = "crate::NullableSchema<SafeErrorMessage>", required)]
2016 reason: Option<SafeErrorMessage>,
2017 },
2018 ModelAttemptStarted {
2019 attempt_id: AttemptId,
2020 #[schemars(range(min = 1))]
2021 attempt_ordinal: u32,
2022 fallback_index: u32,
2023 retry_ordinal: u32,
2024 resolved_model: ResolvedModelRef,
2025 prompt_fingerprint: Sha256Digest,
2026 },
2027 ModelRequestPrepared {
2028 attempt_id: AttemptId,
2029 prompt_fingerprint: Sha256Digest,
2030 },
2031 TextDelta {
2032 attempt_id: AttemptId,
2033 text: String,
2034 },
2035 ReasoningDelta {
2036 attempt_id: AttemptId,
2037 text: String,
2038 },
2039 AttemptAbandoned {
2040 attempt_id: AttemptId,
2041 #[serde(default, skip_serializing_if = "Option::is_none")]
2044 model_error: Option<ModelErrorSummary>,
2045 },
2046 ModelReplayEvaluated {
2047 attempt_id: AttemptId,
2048 resolved_model: ResolvedModelRef,
2049 #[schemars(length(max = 4096))]
2050 ordered_decisions: Vec<ReplayDecision>,
2051 },
2052 ModelTurnCommitted {
2053 attempt_id: AttemptId,
2054 #[schemars(range(min = 1))]
2055 model_turn_seq: u64,
2056 resolved_model: ResolvedModelRef,
2057 input_through_seq: u64,
2058 turn: PersistedModelTurn,
2059 #[schemars(length(max = 256))]
2060 warnings: Vec<SafeErrorMessage>,
2061 },
2062 ModelUsageRecorded {
2063 #[schemars(range(min = 1))]
2064 model_turn_seq: u64,
2065 agent_id: AgentId,
2066 resolved_model: ResolvedModelRef,
2067 usage: Usage,
2068 #[serde(default)]
2069 estimated_cost_pico_usd: Option<u64>,
2070 },
2071 ModelFallback {
2072 from: ResolvedModelRef,
2073 to: ResolvedModelRef,
2074 from_fallback_index: u32,
2075 to_fallback_index: u32,
2076 #[schemars(range(min = 1))]
2077 attempts_on_from: u32,
2078 error: ModelErrorSummary,
2079 },
2080 ToolCallStarted {
2081 #[serde(flatten)]
2082 start: ToolCallStart,
2083 },
2084 ToolCallProgress {
2085 tool_call_id: ToolCallId,
2086 message: SafeDisplayText,
2087 #[serde(
2088 default,
2089 alias = "output_chunk",
2090 deserialize_with = "crate::tool_output::deserialize_delta_display"
2091 )]
2092 display: Option<String>,
2093 },
2094 ToolCallTerminated {
2095 #[serde(flatten)]
2096 termination: ToolCallTermination,
2097 },
2098 ToolOutputElided {
2099 tool_call_id: ToolCallId,
2100 original_bytes: u64,
2101 retained: ArtifactReference,
2102 },
2103 ToolStdinSubmitted {
2104 tool_call_id: ToolCallId,
2105 byte_count: u64,
2106 },
2107 ToolCallLinked {
2108 tool_call_id: ToolCallId,
2109 child_session_id: SessionId,
2110 },
2111 DelegationReserved {
2112 reservation: DelegationReservation,
2113 child_agent: Box<AgentSnapshot>,
2114 manifest_revision: ModelSnapshotRevision,
2115 runtime_revision: RuntimeRevision,
2116 catalog_revision: CatalogRevision,
2117 provider_state_revision: ProviderStateRevision,
2118 model_revision: ModelRevision,
2119 agent_revision: AgentRevision,
2120 recipe_registry_revision: RecipeRegistryRevision,
2121 #[schemars(length(min = 1, max = 256))]
2122 selected_suffix: Vec<FrozenModelBinding>,
2123 #[serde(default)]
2124 #[schemars(length(max = 256))]
2125 cache_strategies: Vec<Option<FrozenCacheStrategy>>,
2126 request_fingerprint: Sha256Digest,
2127 request: DelegateRequestPayload,
2128 },
2129 DelegationStarted {
2130 invocation_id: InvocationId,
2131 child_session_id: SessionId,
2132 },
2133 DelegationRunStarted {
2134 invocation_id: InvocationId,
2135 child_run_id: RunId,
2136 },
2137 DelegationRunAttached {
2138 invocation_id: InvocationId,
2139 child_run_id: RunId,
2140 },
2141 DelegationFinished {
2142 invocation_id: InvocationId,
2143 child_session_id: SessionId,
2144 #[serde(deserialize_with = "deserialize_required_option")]
2145 #[schemars(with = "crate::NullableSchema<RunId>", required)]
2146 child_run_id: Option<RunId>,
2147 status: SessionStatus,
2148 #[serde(deserialize_with = "deserialize_required_option")]
2149 #[schemars(with = "crate::NullableSchema<SafeErrorMessage>", required)]
2150 reason: Option<SafeErrorMessage>,
2151 },
2152 DelegateQueued {
2153 session_id: SessionId,
2154 #[serde(deserialize_with = "deserialize_required_option")]
2155 #[schemars(with = "crate::NullableSchema<u32>", required)]
2156 position: Option<u32>,
2157 },
2158 DelegateFinished {
2159 session_id: SessionId,
2160 #[serde(default, skip_serializing_if = "Option::is_none")]
2163 short_id: Option<String>,
2164 status: SessionStatus,
2165 #[schemars(length(max = 2048))]
2166 preview: String,
2167 total_lines: u64,
2168 },
2169 DelegateFinishedV2 {
2170 invocation_id: InvocationId,
2171 session_id: SessionId,
2172 #[serde(default, skip_serializing_if = "Option::is_none")]
2175 short_id: Option<String>,
2176 status: SessionStatus,
2177 #[schemars(length(max = 2048))]
2178 preview: String,
2179 total_lines: u64,
2180 },
2181 DelegateChildTerminated {
2182 status: SessionStatus,
2183 #[serde(deserialize_with = "deserialize_required_option")]
2184 #[schemars(with = "crate::NullableSchema<SafeErrorMessage>", required)]
2185 reason: Option<SafeErrorMessage>,
2186 },
2187 ApprovalRequested {
2188 request: ApprovalRequest,
2189 },
2190 ApprovalEvaluated {
2191 approval_id: ApprovalId,
2192 decision: ApprovalInternalDecision,
2193 },
2194 ApprovalEscalated {
2195 approval_id: ApprovalId,
2196 reason_code: ApprovalReasonCode,
2197 },
2198 ApprovalUserDecisionRecorded {
2199 approval_id: ApprovalId,
2200 client_response_id: ClientResponseId,
2201 decision: ApprovalUserDecision,
2202 #[serde(deserialize_with = "deserialize_required_option")]
2203 #[schemars(with = "crate::NullableSchema<ApprovalFeedback>", required)]
2204 feedback: Option<ApprovalFeedback>,
2205 },
2206 ApprovalFinalized {
2207 approval_id: ApprovalId,
2208 decision: ApprovalFinalDecision,
2209 },
2210 ApprovalCancelled {
2211 approval_id: ApprovalId,
2212 reason_code: ApprovalReasonCode,
2213 },
2214 ApprovalDoomLoopDetected {
2215 approval_id: ApprovalId,
2216 operation_fingerprint: OperationFingerprint,
2217 #[schemars(range(min = 4))]
2218 repetitions: u32,
2219 },
2220 TreeApprovalGrantCommitted {
2221 grant: TreeApprovalGrant,
2222 },
2223 InternalAgentStarted {
2224 invocation_id: InternalAgentInvocationId,
2225 internal_run_id: InternalAgentRunId,
2226 kind: InternalAgentKind,
2227 backend: InternalAgentBackend,
2228 call: SafeInternalAgentCall,
2229 },
2230 InternalAgentCompleted {
2231 invocation_id: InternalAgentInvocationId,
2232 internal_run_id: InternalAgentRunId,
2233 kind: InternalAgentKind,
2234 result: SafeInternalAgentResult,
2235 },
2236 InternalAgentUsageRecorded {
2237 internal_run_id: InternalAgentRunId,
2238 kind: InternalAgentKind,
2239 agent_id: AgentId,
2240 resolved_model: ResolvedModelRef,
2241 usage: Usage,
2242 #[serde(default)]
2243 estimated_cost_pico_usd: Option<u64>,
2244 },
2245 InternalAgentFailed {
2246 invocation_id: InternalAgentInvocationId,
2247 internal_run_id: InternalAgentRunId,
2248 kind: InternalAgentKind,
2249 failure: InternalAgentFailure,
2250 },
2251 InternalAgentCancelled {
2252 invocation_id: InternalAgentInvocationId,
2253 internal_run_id: InternalAgentRunId,
2254 kind: InternalAgentKind,
2255 #[serde(deserialize_with = "deserialize_required_option")]
2256 #[schemars(with = "crate::NullableSchema<SafeErrorMessage>", required)]
2257 reason: Option<SafeErrorMessage>,
2258 },
2259 InternalAgentInterrupted {
2260 invocation_id: InternalAgentInvocationId,
2261 internal_run_id: InternalAgentRunId,
2262 kind: InternalAgentKind,
2263 #[serde(deserialize_with = "deserialize_required_option")]
2264 #[schemars(with = "crate::NullableSchema<SafeErrorMessage>", required)]
2265 reason: Option<SafeErrorMessage>,
2266 },
2267 InternalAgentFallback {
2268 invocation_id: InternalAgentInvocationId,
2269 internal_run_id: InternalAgentRunId,
2270 kind: InternalAgentKind,
2271 from: InternalAgentBackend,
2272 to: InternalAgentBackend,
2273 failure: InternalAgentFailure,
2274 #[schemars(range(min = 1))]
2275 attempts: u32,
2276 },
2277 ContextCheckpointCommitted {
2278 #[serde(flatten)]
2279 commit: ContextCheckpointCommit,
2280 },
2281 ContextRehydrated {
2282 #[schemars(length(max = 5))]
2283 files: Vec<ContextRehydratedFile>,
2284 },
2285 SessionTitleCommitted {
2286 change: SessionTitleChange,
2287 input_through_seq: u64,
2288 },
2289}
2290impl EventPayload {
2291 fn requires_run_id(&self) -> bool {
2292 !matches!(
2293 self,
2294 Self::SessionCreated { .. }
2295 | Self::SessionReverted { .. }
2296 | Self::SessionPermissionOverlaySet { .. }
2297 | Self::SkillLoaded { .. }
2298 | Self::SkillInvocationNoted { .. }
2299 | Self::PluginEventAdded { .. }
2300 | Self::PluginDiagnostic { .. }
2301 | Self::GoalActivated { .. }
2302 | Self::GoalChecklistRevised { .. }
2303 | Self::GoalLifecycleChanged { .. }
2304 | Self::ProducerMessageAccepted { .. }
2305 | Self::ProducerMessageDiscarded { .. }
2306 | Self::SessionTitleCommitted { .. }
2307 | Self::UserInputAdmitted { .. }
2308 | Self::UserInputRecalled { .. }
2309 | Self::DelegatedContextSeeded { .. }
2310 | Self::DelegateChildTerminated { .. }
2311 )
2312 }
2313 fn validate(&self) -> Result<(), EventSchemaError> {
2314 match self {
2315 Self::GoalActivated { objective, .. } if objective.trim().is_empty() => {
2316 return Err(EventSchemaError::InvalidGoalEvent);
2317 }
2318 Self::GoalLifecycleChanged { status, selection: Some(_), .. }
2319 if *status != GoalStatus::Active => {
2320 return Err(EventSchemaError::InvalidGoalEvent);
2321 }
2322 Self::GoalChecklistRevised { items, .. } => {
2323 if items.iter().any(|item| item.description.trim().is_empty()) {
2324 return Err(EventSchemaError::InvalidGoalEvent);
2325 }
2326 }
2327 Self::ProducerMessageAccepted { producer_owner, reminder, .. } => {
2328 if matches!(producer_owner, ProducerOwner::Plugin { plugin } if plugin.trim().is_empty())
2329 || reminder.as_ref().is_some_and(|identity| {
2330 !matches!(producer_owner, ProducerOwner::Goal { goal_id } if *goal_id == identity.goal_id)
2331 })
2332 {
2333 return Err(EventSchemaError::InvalidProducerMessage);
2334 }
2335 }
2336 Self::ProducerMessagesClaimed { message_ids }
2337 if message_ids.is_empty()
2338 || message_ids.iter().copied().collect::<HashSet<_>>().len()
2339 != message_ids.len() =>
2340 {
2341 return Err(EventSchemaError::InvalidProducerMessage);
2342 }
2343 Self::ProducerMessagesReleased { claim_seq } if *claim_seq == 0 => {
2344 return Err(EventSchemaError::InvalidProducerMessage);
2345 }
2346 Self::ProducerMessageDiscarded {
2347 reminder,
2348 producer_owner,
2349 ..
2350 } if reminder.is_none() && producer_owner.is_none() => {
2351 return Err(EventSchemaError::InvalidProducerMessage);
2352 }
2353 Self::SessionCreated {
2354 creation_selection,
2355 creation_agent,
2356 ..
2357 } => {
2358 creation_agent
2359 .validate_selection(creation_selection)
2360 .map_err(|_| EventSchemaError::SelectionAgentMismatch)?;
2361 }
2362 Self::SessionReverted { through_seq } => {
2363 if *through_seq == 0 {
2364 return Err(EventSchemaError::InvalidRevertSequence);
2365 }
2366 }
2367 Self::SessionPermissionOverlaySet { overlay } => overlay
2368 .validate()
2369 .map_err(|_| EventSchemaError::InvalidSessionPermissionOverlay)?,
2370 Self::SkillLoaded {
2371 name,
2372 rendered_body,
2373 source_path,
2374 base_dir,
2375 supporting_files,
2376 ..
2377 } => {
2378 if name.is_empty()
2379 || rendered_body.is_empty()
2380 || source_path.is_empty()
2381 || base_dir.is_empty()
2382 || supporting_files.len() > 10
2383 {
2384 return Err(EventSchemaError::InvalidSkillEvent);
2385 }
2386 }
2387 Self::SkillInvocationNoted { name } if name.is_empty() => {
2388 return Err(EventSchemaError::InvalidSkillEvent);
2389 }
2390 Self::AgentMdLoaded { entries } => {
2391 if entries.is_empty() || entries.len() > 2 {
2392 return Err(EventSchemaError::InvalidAgentMd);
2393 }
2394 for entry in entries {
2395 entry.validate()?;
2396 }
2397 }
2398 Self::AgentMdSkipped {
2399 path,
2400 byte_length,
2401 } => {
2402 if byte_length <= &AgentMdSkipped::MAX_SKIP_BYTES || path.as_str().is_empty() {
2403 return Err(EventSchemaError::InvalidAgentMd);
2404 }
2405 }
2406 Self::RunStarted {
2407 selection,
2408 agent,
2409 selected_suffix,
2410 internal_agents,
2411 ..
2412 } => {
2413 agent
2414 .validate_selected_suffix(selection, selected_suffix)
2415 .map_err(|_| EventSchemaError::InvalidSelectedSuffix)?;
2416 if !matches!(internal_agents.len(), 0 | 3)
2417 || internal_agents
2418 .windows(2)
2419 .any(|pair| pair[0].kind >= pair[1].kind)
2420 || internal_agents.iter().any(|definition| {
2421 definition.validate().is_err()
2422 || definition.agent.as_str()
2423 != match definition.kind {
2424 InternalAgentKind::Approval => "approval",
2425 InternalAgentKind::ContextCompaction => "compaction",
2426 InternalAgentKind::SessionTitle => "title",
2427 }
2428 })
2429 {
2430 return Err(EventSchemaError::InvalidAgentSnapshot);
2431 }
2432 }
2433 Self::MessageInjected { role, input }
2434 if input.trim().is_empty() || *role == crate::ExtensionMessageRole::Tool =>
2435 {
2436 return Err(EventSchemaError::InvalidJson);
2437 }
2438 Self::UserInputTransformed {
2439 original_input,
2440 input,
2441 } if original_input.trim().is_empty()
2442 || input.trim().is_empty()
2443 || original_input == input =>
2444 {
2445 return Err(EventSchemaError::InvalidJson);
2446 }
2447 Self::ModelAttemptStarted {
2448 attempt_ordinal,
2449 resolved_model,
2450 ..
2451 } => {
2452 if *attempt_ordinal == 0 {
2453 return Err(EventSchemaError::InvalidAttemptOrdinal);
2454 }
2455 resolved_model
2456 .validate()
2457 .map_err(|_| EventSchemaError::InvalidResolvedModel)?;
2458 }
2459 Self::ModelReplayEvaluated {
2460 resolved_model,
2461 ordered_decisions,
2462 ..
2463 } => {
2464 if ordered_decisions.len() > 4096 {
2465 return Err(EventSchemaError::TooManyReplayDecisions);
2466 }
2467 resolved_model
2468 .validate()
2469 .map_err(|_| EventSchemaError::InvalidResolvedModel)?;
2470 }
2471 Self::ModelTurnCommitted {
2472 model_turn_seq,
2473 resolved_model,
2474 turn,
2475 warnings,
2476 ..
2477 } => {
2478 if *model_turn_seq == 0 || warnings.len() > 256 {
2479 return Err(EventSchemaError::InvalidModelTurnCommit);
2480 }
2481 resolved_model
2482 .validate()
2483 .map_err(|_| EventSchemaError::InvalidResolvedModel)?;
2484 turn.validate_for(resolved_model)?;
2485 }
2486 Self::ModelUsageRecorded {
2487 model_turn_seq,
2488 resolved_model,
2489 ..
2490 } => {
2491 if *model_turn_seq == 0 {
2492 return Err(EventSchemaError::InvalidModelTurnCommit);
2493 }
2494 resolved_model
2495 .validate()
2496 .map_err(|_| EventSchemaError::InvalidResolvedModel)?;
2497 }
2498 Self::InternalAgentUsageRecorded { resolved_model, .. } => {
2499 resolved_model
2500 .validate()
2501 .map_err(|_| EventSchemaError::InvalidResolvedModel)?;
2502 }
2503 Self::ModelFallback {
2504 from,
2505 to,
2506 from_fallback_index,
2507 to_fallback_index,
2508 attempts_on_from,
2509 ..
2510 } => {
2511 if to_fallback_index <= from_fallback_index
2512 || *attempts_on_from == 0
2513 || from.selection.model == to.selection.model
2514 {
2515 return Err(EventSchemaError::InvalidFallback);
2516 }
2517 from.validate()
2518 .map_err(|_| EventSchemaError::InvalidResolvedModel)?;
2519 to.validate()
2520 .map_err(|_| EventSchemaError::InvalidResolvedModel)?;
2521 }
2522 Self::ToolCallStarted { start } => {
2523 start.output.validate().map_err(|_| EventSchemaError::InvalidToolTermination)?;
2524 if start.owner.model_turn_seq == 0 {
2525 return Err(EventSchemaError::ZeroModelTurnSequence);
2526 }
2527 }
2528 Self::ToolCallProgress { display, .. } => {
2529 if display.as_ref().is_some_and(|text| !crate::tool_output::validate_display(text, SafeDisplayText::MAX_BYTES)) {
2530 return Err(EventSchemaError::ToolOutputTooLarge);
2531 }
2532 }
2533 Self::ToolCallTerminated { termination } => termination.validate()?,
2534 Self::DelegationReserved {
2535 child_agent,
2536 selected_suffix,
2537 cache_strategies,
2538 request,
2539 ..
2540 } => {
2541 request.validate()?;
2542 if (!cache_strategies.is_empty() && cache_strategies.len() != selected_suffix.len())
2543 || cache_strategies
2544 .iter()
2545 .flatten()
2546 .any(|strategy| strategy.validate().is_err())
2547 {
2548 return Err(EventSchemaError::InvalidDelegationLifecycle);
2549 }
2550 child_agent
2551 .validate_selected_suffix(
2552 &RunSelection {
2553 agent: child_agent.agent.clone(),
2554 model: selected_suffix
2555 .first()
2556 .ok_or(EventSchemaError::InvalidDelegationLifecycle)?
2557 .selection
2558 .clone(),
2559 preset: None,
2560 },
2561 selected_suffix,
2562 )
2563 .map_err(|_| EventSchemaError::InvalidDelegationLifecycle)?;
2564 }
2565 Self::DelegationFinished { status, .. }
2566 if !matches!(
2567 status,
2568 SessionStatus::Completed
2569 | SessionStatus::Failed
2570 | SessionStatus::Interrupted
2571 | SessionStatus::Cancelled
2572 ) =>
2573 {
2574 return Err(EventSchemaError::InvalidDelegationLifecycle);
2575 }
2576 Self::DelegatedContextSeeded { turns, .. } => {
2577 if turns.is_empty()
2578 || turns.len() > 65_536
2579 || turns.iter().any(|turn| turn.text.is_empty())
2580 || turns.iter().map(|turn| turn.text.len()).sum::<usize>() > 65_536
2581 {
2582 return Err(EventSchemaError::InvalidDelegatedContext);
2583 }
2584 }
2585 Self::DelegateFinished {
2586 status, preview, ..
2587 }
2588 | Self::DelegateFinishedV2 {
2589 status, preview, ..
2590 } => {
2591 if !matches!(
2592 status,
2593 SessionStatus::Completed
2594 | SessionStatus::Failed
2595 | SessionStatus::Interrupted
2596 | SessionStatus::Cancelled
2597 ) || preview.len() > 2048
2598 {
2599 return Err(EventSchemaError::InvalidDelegateFinished);
2600 }
2601 }
2602 Self::DelegateChildTerminated { status, .. }
2603 if !matches!(status, SessionStatus::Failed | SessionStatus::Cancelled) =>
2604 {
2605 return Err(EventSchemaError::InvalidDelegateFinished);
2606 }
2607 Self::ToolOutputElided {
2608 original_bytes,
2609 retained,
2610 ..
2611 } => {
2612 if *original_bytes == 0 {
2613 return Err(EventSchemaError::InvalidToolElision);
2614 }
2615 retained.validate()?;
2616 }
2617 Self::ApprovalEvaluated { decision, .. } => {
2618 decision
2619 .validate()
2620 .map_err(|_| EventSchemaError::InvalidApprovalLifecycle)?;
2621 }
2622 Self::ApprovalEscalated { reason_code, .. }
2623 if *reason_code != ApprovalReasonCode::Escalated =>
2624 {
2625 return Err(EventSchemaError::InvalidApprovalLifecycle);
2626 }
2627 Self::ApprovalUserDecisionRecorded {
2628 decision, feedback, ..
2629 } if feedback.is_some() && *decision != ApprovalUserDecision::Reject => {
2630 return Err(EventSchemaError::InvalidApprovalLifecycle);
2631 }
2632 Self::ApprovalFinalized { decision, .. } => decision
2633 .validate()
2634 .map_err(|_| EventSchemaError::InvalidApprovalLifecycle)?,
2635 Self::ApprovalCancelled { reason_code, .. }
2636 if !matches!(
2637 reason_code,
2638 ApprovalReasonCode::UserCancelled
2639 | ApprovalReasonCode::RequestCancelled
2640 | ApprovalReasonCode::ApprovalExpired
2641 | ApprovalReasonCode::OperationChanged
2642 | ApprovalReasonCode::PreparedCapabilityLost
2643 ) =>
2644 {
2645 return Err(EventSchemaError::InvalidApprovalLifecycle);
2646 }
2647 Self::ApprovalDoomLoopDetected { repetitions, .. } if *repetitions < 4 => {
2648 return Err(EventSchemaError::InvalidApprovalCounter);
2649 }
2650 Self::TreeApprovalGrantCommitted { grant } => grant
2651 .validate()
2652 .map_err(|_| EventSchemaError::InvalidApprovalLifecycle)?,
2653 Self::InternalAgentStarted { backend, .. } => validate_internal_backend(backend)?,
2654 Self::InternalAgentFailed { failure, .. } => validate_internal_failure(failure)?,
2655 Self::InternalAgentFallback {
2656 from,
2657 to,
2658 failure,
2659 attempts,
2660 ..
2661 } => {
2662 if from == to || *attempts == 0 {
2663 return Err(EventSchemaError::InvalidInternalAgentLifecycle);
2664 }
2665 validate_internal_backend(from)?;
2666 validate_internal_backend(to)?;
2667 validate_internal_failure(failure)?;
2668 }
2669 Self::ContextCheckpointCommitted { commit } => commit.validate()?,
2670 Self::ContextRehydrated { files } => {
2671 if files.is_empty() || files.len() > 5 {
2672 return Err(EventSchemaError::InvalidContextRehydration);
2673 }
2674 for file in files {
2675 file.validate()?;
2676 }
2677 }
2678 _ => {}
2679 }
2680 Ok(())
2681 }
2682}
2683
2684#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, JsonSchema, PartialEq, Serialize)]
2685#[serde(rename_all = "snake_case")]
2686pub enum PluginDiagnosticKind {
2687 EventDrop,
2688 InterceptionTimeout,
2689 InterceptionCrash,
2690 HookBlocked,
2691 OversizedEvent,
2692 InvalidModification,
2693 UnsupportedCapability,
2694 ContextMismatch,
2695 RateLimited,
2696 RecoveryFailed,
2697 RecoveryDisabled,
2698}
2699
2700#[derive(Clone, Debug, JsonSchema, PartialEq, Serialize)]
2701#[serde(deny_unknown_fields)]
2702pub struct StoredEvent {
2703 #[serde(default, skip_serializing_if = "Option::is_none")]
2704 pub engine_version: Option<String>,
2705 #[serde(default, skip_serializing_if = "Option::is_none")]
2706 pub origin: Option<EventOrigin>,
2707 pub session_id: SessionId,
2708 #[serde(deserialize_with = "crate::deserialize_required_option")]
2709 #[schemars(with = "crate::NullableSchema<RunId>", required)]
2710 pub run_id: Option<RunId>,
2711 #[schemars(range(min = 1))]
2712 pub seq: u64,
2713 pub timestamp: Timestamp,
2714 pub payload: EventPayload,
2715}
2716impl StoredEvent {
2717 pub fn validate(&self) -> Result<(), EventSchemaError> {
2718 if let Some(origin) = &self.origin {
2719 EventOrigin::new(origin.as_str())?;
2720 }
2721 if self.seq == 0 {
2722 return Err(EventSchemaError::ZeroEventSequence);
2723 }
2724 if matches!(self.payload, EventPayload::SessionCreated { .. })
2725 && (self.seq != 1 || self.run_id.is_some())
2726 {
2727 return Err(EventSchemaError::InvalidSessionCreatedEnvelope);
2728 }
2729 if self.payload.requires_run_id() && self.run_id.is_none() {
2730 return Err(EventSchemaError::MissingRunId);
2731 }
2732 if let EventPayload::ProducerMessageConsumed { run_id, .. } = &self.payload
2733 && self.run_id != Some(*run_id)
2734 {
2735 return Err(EventSchemaError::InvalidProducerMessage);
2736 }
2737 self.payload.validate()?;
2738 Ok(())
2739 }
2740}
2741
2742#[must_use]
2745pub fn visible_events(events: &[StoredEvent]) -> Vec<StoredEvent> {
2746 let mut visible = Vec::new();
2747 let mut historical_ceiling = u64::MAX;
2748 for event in events {
2749 if let EventPayload::SessionReverted { through_seq } = &event.payload {
2750 historical_ceiling = historical_ceiling.min(*through_seq);
2751 visible.retain(|candidate: &StoredEvent| candidate.seq <= historical_ceiling);
2752 visible.push(event.clone());
2753 } else {
2754 visible.push(event.clone());
2755 }
2756 }
2757 visible
2758}
2759impl<'de> Deserialize<'de> for StoredEvent {
2760 fn deserialize<D>(d: D) -> Result<Self, D::Error>
2761 where
2762 D: serde::Deserializer<'de>,
2763 {
2764 #[derive(Deserialize)]
2765 #[serde(deny_unknown_fields)]
2766 struct Wire {
2767 #[serde(default)]
2768 engine_version: Option<String>,
2769 #[serde(default)]
2770 origin: Option<EventOrigin>,
2771 #[serde(default)]
2772 event_schema_version: Option<Value>,
2773 session_id: SessionId,
2774 #[serde(deserialize_with = "crate::deserialize_required_option")]
2775 run_id: Option<RunId>,
2776 seq: u64,
2777 timestamp: Timestamp,
2778 payload: Value,
2779 }
2780 let w = Wire::deserialize(d)?;
2781 let _ = w.event_schema_version;
2782 let value = Self {
2783 engine_version: w.engine_version,
2784 origin: w.origin,
2785 session_id: w.session_id,
2786 run_id: w.run_id,
2787 seq: w.seq,
2788 timestamp: w.timestamp,
2789 payload: deserialize_event_payload_best_effort(w.payload)
2790 .map_err(serde::de::Error::custom)?
2791 .payload,
2792 };
2793 value.validate().map_err(serde::de::Error::custom)?;
2794 Ok(value)
2795 }
2796}
2797
2798const MAX_DEGRADED_FIELDS: usize = 8;
2799
2800#[derive(Clone, Debug, PartialEq)]
2801pub struct EventPayloadRead {
2802 pub payload: EventPayload,
2803 pub degraded_fields: Vec<String>,
2804}
2805
2806pub fn deserialize_event_payload_best_effort(mut value: Value) -> Result<EventPayloadRead, String> {
2811 let mut degraded_fields = Vec::new();
2812 if value.get("type").and_then(Value::as_str) == Some("goal_checklist_revised")
2813 && let Some(items) = value.get_mut("items").and_then(Value::as_array_mut)
2814 {
2815 let mut removed_item_ids = false;
2816 for item in items.iter_mut().filter_map(Value::as_object_mut) {
2817 removed_item_ids |= item.remove("id").is_some();
2818 }
2819 if removed_item_ids {
2820 degraded_fields.push("items[].id".into());
2821 }
2822 }
2823 let mut previous_path = None;
2824 for _ in 0..=MAX_DEGRADED_FIELDS {
2825 match serde_path_to_error::deserialize::<_, EventPayload>(value.clone().into_deserializer())
2826 {
2827 Ok(payload) => {
2828 return Ok(EventPayloadRead {
2829 payload,
2830 degraded_fields,
2831 });
2832 }
2833 Err(error) => {
2834 let path = error.path().to_string();
2835 if path != "." && previous_path.as_deref() == Some(path.as_str()) {
2836 return Err(error.to_string());
2837 }
2838 let degraded = if path == "." {
2839 replace_degradable_leaf(&mut value, &error.to_string())
2840 } else if replace_path_with_null(&mut value, error.path()) {
2841 Some(path.clone())
2842 } else {
2843 None
2844 };
2845 let Some(degraded) = degraded else {
2846 return Err(error.to_string());
2847 };
2848 previous_path = Some(path.clone());
2849 degraded_fields.push(degraded);
2850 }
2851 }
2852 }
2853 Err(format!(
2854 "event payload exceeds the {MAX_DEGRADED_FIELDS}-field degradation limit"
2855 ))
2856}
2857
2858#[derive(Clone)]
2859enum ValuePathSegment {
2860 Key(String),
2861 Index(usize),
2862}
2863
2864fn replace_degradable_leaf(value: &mut Value, original_error: &str) -> Option<String> {
2865 let mut paths = Vec::new();
2866 collect_leaf_paths(value, &mut Vec::new(), &mut paths);
2867 for path in paths {
2868 let mut candidate = value.clone();
2869 if !set_value_path(&mut candidate, &path, Value::Null) {
2870 continue;
2871 }
2872 match serde_path_to_error::deserialize::<_, EventPayload>(
2873 candidate.clone().into_deserializer(),
2874 ) {
2875 Ok(_) => {
2876 *value = candidate;
2877 return Some(display_value_path(&path));
2878 }
2879 Err(error)
2880 if error.to_string() != original_error
2881 && !error.inner().to_string().contains("invalid type: null") =>
2882 {
2883 *value = candidate;
2884 return Some(display_value_path(&path));
2885 }
2886 Err(_) => {}
2887 }
2888 }
2889 let missing = original_error
2890 .split("missing field `")
2891 .nth(1)
2892 .and_then(|suffix| suffix.split('`').next())?;
2893 let mut object_paths = Vec::new();
2894 collect_object_paths(value, &mut Vec::new(), &mut object_paths);
2895 for mut path in object_paths {
2896 path.push(ValuePathSegment::Key(missing.to_owned()));
2897 let mut candidate = value.clone();
2898 if !set_value_path(&mut candidate, &path, Value::Null) {
2899 continue;
2900 }
2901 if serde_json::from_value::<EventPayload>(candidate.clone()).is_ok() {
2902 *value = candidate;
2903 return Some(display_value_path(&path));
2904 }
2905 }
2906 None
2907}
2908
2909fn collect_leaf_paths(
2910 value: &Value,
2911 current: &mut Vec<ValuePathSegment>,
2912 paths: &mut Vec<Vec<ValuePathSegment>>,
2913) {
2914 match value {
2915 Value::Object(object) => {
2916 for (key, child) in object {
2917 if key == "type" || child.is_null() {
2918 continue;
2919 }
2920 current.push(ValuePathSegment::Key(key.clone()));
2921 collect_leaf_paths(child, current, paths);
2922 current.pop();
2923 }
2924 }
2925 Value::Array(array) => {
2926 for (index, child) in array.iter().enumerate() {
2927 current.push(ValuePathSegment::Index(index));
2928 collect_leaf_paths(child, current, paths);
2929 current.pop();
2930 }
2931 }
2932 _ => paths.push(current.clone()),
2933 }
2934}
2935
2936fn collect_object_paths(
2937 value: &Value,
2938 current: &mut Vec<ValuePathSegment>,
2939 paths: &mut Vec<Vec<ValuePathSegment>>,
2940) {
2941 match value {
2942 Value::Object(object) => {
2943 paths.push(current.clone());
2944 for (key, child) in object {
2945 current.push(ValuePathSegment::Key(key.clone()));
2946 collect_object_paths(child, current, paths);
2947 current.pop();
2948 }
2949 }
2950 Value::Array(array) => {
2951 for (index, child) in array.iter().enumerate() {
2952 current.push(ValuePathSegment::Index(index));
2953 collect_object_paths(child, current, paths);
2954 current.pop();
2955 }
2956 }
2957 _ => {}
2958 }
2959}
2960
2961fn set_value_path(value: &mut Value, path: &[ValuePathSegment], replacement: Value) -> bool {
2962 let Some((leaf, parents)) = path.split_last() else {
2963 return false;
2964 };
2965 let mut current = value;
2966 for segment in parents {
2967 let Some(next) = (match segment {
2968 ValuePathSegment::Key(key) => current.get_mut(key),
2969 ValuePathSegment::Index(index) => current.get_mut(*index),
2970 }) else {
2971 return false;
2972 };
2973 current = next;
2974 }
2975 match leaf {
2976 ValuePathSegment::Key(key) => current
2977 .as_object_mut()
2978 .map(|object| object.insert(key.clone(), replacement))
2979 .is_some(),
2980 ValuePathSegment::Index(index) => current
2981 .as_array_mut()
2982 .and_then(|array| array.get_mut(*index))
2983 .map(|value| *value = replacement)
2984 .is_some(),
2985 }
2986}
2987
2988fn display_value_path(path: &[ValuePathSegment]) -> String {
2989 let mut result = String::new();
2990 for segment in path {
2991 match segment {
2992 ValuePathSegment::Key(key) => {
2993 if !result.is_empty() {
2994 result.push('.');
2995 }
2996 result.push_str(key);
2997 }
2998 ValuePathSegment::Index(index) => result.push_str(&format!("[{index}]")),
2999 }
3000 }
3001 result
3002}
3003
3004fn replace_path_with_null(value: &mut Value, path: &serde_path_to_error::Path) -> bool {
3005 let segments = path
3006 .iter()
3007 .filter(|segment| !matches!(segment, Segment::Enum { .. }))
3008 .collect::<Vec<_>>();
3009 let Some((leaf, parents)) = segments.split_last() else {
3010 return false;
3011 };
3012 let mut current = value;
3013 for segment in parents {
3014 let next = match segment {
3015 Segment::Map { key } => current.get_mut(key),
3016 Segment::Seq { index } => current.get_mut(*index),
3017 Segment::Enum { .. } | Segment::Unknown => None,
3018 };
3019 let Some(next) = next else {
3020 return false;
3021 };
3022 current = next;
3023 }
3024 match leaf {
3025 Segment::Map { key } => {
3026 let Some(object) = current.as_object_mut() else {
3027 return false;
3028 };
3029 object.remove(key);
3030 object.insert(key.clone(), Value::Null);
3031 true
3032 }
3033 Segment::Seq { index } => current
3034 .as_array_mut()
3035 .and_then(|array| array.get_mut(*index))
3036 .map(|leaf| *leaf = Value::Null)
3037 .is_some(),
3038 Segment::Enum { .. } | Segment::Unknown => false,
3039 }
3040}
3041
3042#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
3043#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
3044pub enum EventSubscriptionMessage {
3045 Event {
3046 event: Box<StoredEvent>,
3047 },
3048 Gap {
3049 session_id: SessionId,
3050 last_delivered_seq: u64,
3051 },
3052}
3053#[derive(Clone, Debug, Deserialize, Eq, Hash, JsonSchema, PartialEq, Serialize)]
3054#[serde(rename_all = "snake_case")]
3055pub enum OutputStream {
3056 Stdout,
3057 Stderr,
3058 Single,
3059 Named(#[serde(deserialize_with = "crate::tool_output::deserialize_stream_name")] String),
3060}
3061
3062impl OutputStream {
3063 pub fn name(&self) -> &str {
3064 match self {
3065 Self::Stdout => "stdout",
3066 Self::Stderr => "stderr",
3067 Self::Single => "",
3068 Self::Named(name) => name,
3069 }
3070 }
3071
3072 pub fn from_channel(name: Option<&str>) -> Self {
3073 match name {
3074 None => Self::Single,
3075 Some("stdout") => Self::Stdout,
3076 Some("stderr") => Self::Stderr,
3077 Some(name) => Self::Named(name.to_owned()),
3078 }
3079 }
3080}
3081#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
3082#[serde(deny_unknown_fields)]
3083pub struct OutputDelta {
3084 pub call_id: ToolCallId,
3085 pub stream: OutputStream,
3086 pub byte_offset: u64,
3087 pub data: String,
3088}
3089#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
3090#[serde(deny_unknown_fields)]
3091pub struct OutputGap {
3092 pub call_id: ToolCallId,
3093 pub stream: OutputStream,
3094 pub next_offset: u64,
3095}
3096#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
3097#[serde(deny_unknown_fields)]
3098pub struct OutputSnapshot {
3099 pub call_id: ToolCallId,
3100 pub start_offset: u64,
3101 pub end_offset: u64,
3102 pub chunks: Vec<OutputDelta>,
3103}
3104#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
3105#[serde(deny_unknown_fields)]
3106pub struct OutputSnapshotEnvelope {
3107 pub stream: OutputStream,
3108 pub snapshot: OutputSnapshot,
3109}
3110
3111#[derive(Clone, Debug, Eq, PartialEq)]
3112pub enum EventSchemaError {
3113 InvalidGoalEvent,
3114 InvalidProducerMessage,
3115 InvalidOrigin,
3116 InvalidAgentMd,
3117 EmptyTitle,
3118 TitleTooLong,
3119 TitleControlCharacter,
3120 InvalidArtifactReference,
3121 InvalidToolElision,
3122 InvalidContextRehydration,
3123 ToolOutputTooLarge,
3124 ToolMetadataTooLarge,
3125 TooManyAttachments,
3126 InvalidAttachmentFilename,
3127 TooManyToolEmittedMessages,
3128 EmptyToolEmittedMessage,
3129 EmptyToolEmittedText,
3130 ToolEmittedTextTooLarge,
3131 InvalidJson,
3132 ZeroModelTurnSequence,
3133 InvalidToolTermination,
3134 InvalidDelegateFinished,
3135 InvalidDelegationLifecycle,
3136 InvalidDelegatedContext,
3137 TooManyModelParts,
3138 ModelTurnTooLarge,
3139 SummaryLimitTooLarge,
3140 SummaryTooLarge,
3141 InvalidCheckpointBoundaries,
3142 NativeSelectionMismatch,
3143 InvalidApprovalLifecycle,
3144 InvalidApprovalCounter,
3145 InvalidInternalAgentLifecycle,
3146 InvalidSessionMetaSequence,
3147 SelectionAgentMismatch,
3148 InvalidAgentSnapshot,
3149 InvalidSelectedSuffix,
3150 InvalidAttemptOrdinal,
3151 InvalidResolvedModel,
3152 TooManyReplayDecisions,
3153 InvalidModelTurnCommit,
3154 InvalidFallback,
3155 InvalidRevertSequence,
3156 InvalidSessionPermissionOverlay,
3157 InvalidSkillEvent,
3158 ZeroEventSequence,
3159 InvalidSessionCreatedEnvelope,
3160 MissingRunId,
3161}
3162impl fmt::Display for EventSchemaError {
3163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
3164 f.write_str(match self {
3165 Self::InvalidGoalEvent => "goal objective/items must be nonblank",
3166 Self::InvalidProducerMessage => {
3167 "producer owner, reminder identity, or consumed run is invalid"
3168 }
3169 Self::InvalidOrigin => "event origin is invalid",
3170 Self::InvalidAgentMd => "AGENTS.md context event is invalid",
3171 Self::EmptyTitle => "session title must not be blank",
3172 Self::TitleTooLong => "session title exceeds 512 bytes",
3173 Self::TitleControlCharacter => "session title must not contain control characters",
3174 Self::InvalidArtifactReference => {
3175 "artifact reference must be a bounded artifact:// URI"
3176 }
3177 Self::InvalidToolElision => "tool output elision is invalid",
3178 Self::InvalidContextRehydration => "context rehydration is invalid",
3179 Self::ToolOutputTooLarge => "persisted tool output exceeds 2 MiB",
3180 Self::ToolMetadataTooLarge => "persisted tool metadata exceeds 2 MiB",
3181 Self::TooManyAttachments => "persisted tool result exceeds 256 attachments",
3182 Self::InvalidAttachmentFilename => "attachment filename is invalid",
3183 Self::TooManyToolEmittedMessages => {
3184 "persisted tool result exceeds 4 additional messages"
3185 }
3186 Self::EmptyToolEmittedMessage => "tool-emitted message content must not be empty",
3187 Self::EmptyToolEmittedText => "tool-emitted text must not be blank",
3188 Self::ToolEmittedTextTooLarge => "tool-emitted message text exceeds 64 KiB",
3189 Self::InvalidJson => "JSON value could not be serialized",
3190 Self::ZeroModelTurnSequence => "model_turn_seq must be positive",
3191 Self::InvalidToolTermination => {
3192 "tool termination outcome/result/error combination is invalid"
3193 }
3194 Self::InvalidDelegateFinished => "delegate completion payload is invalid",
3195 Self::InvalidDelegationLifecycle => "delegation lifecycle payload is invalid",
3196 Self::InvalidDelegatedContext => "delegated context seed is invalid",
3197 Self::TooManyModelParts => "persisted model turn exceeds 4096 parts",
3198 Self::ModelTurnTooLarge => "persisted model turn exceeds 8 MiB",
3199 Self::SummaryLimitTooLarge => "summary limit exceeds 2 MiB",
3200 Self::SummaryTooLarge => "summary exceeds its frozen byte limit",
3201 Self::InvalidCheckpointBoundaries => "context checkpoint boundaries are invalid",
3202 Self::NativeSelectionMismatch => {
3203 "native artifact selection fingerprint does not match resolved model"
3204 }
3205 Self::InvalidApprovalLifecycle => "approval lifecycle fields contradict",
3206 Self::InvalidApprovalCounter => {
3207 "approval lifecycle counter is below its runtime minimum"
3208 }
3209 Self::InvalidInternalAgentLifecycle => "internal-agent lifecycle fields contradict",
3210 Self::InvalidSessionMetaSequence => "session metadata sequence fields are inconsistent",
3211 Self::SelectionAgentMismatch => {
3212 "creation selection agent does not match creation snapshot"
3213 }
3214 Self::InvalidAgentSnapshot => "event contains an invalid agent snapshot",
3215 Self::InvalidSelectedSuffix => "RunStarted selected suffix is invalid",
3216 Self::InvalidAttemptOrdinal => "attempt ordinal must be positive",
3217 Self::InvalidResolvedModel => "event contains an invalid resolved model",
3218 Self::TooManyReplayDecisions => "replay decision list exceeds 4096 entries",
3219 Self::InvalidModelTurnCommit => "model turn sequence or warnings are invalid",
3220 Self::InvalidFallback => {
3221 "fallback must advance to a distinct later model after at least one attempt"
3222 }
3223 Self::InvalidRevertSequence => "session revert sequence must be positive",
3224 Self::InvalidSessionPermissionOverlay => "session permission overlay is invalid",
3225 Self::InvalidSkillEvent => "skill event is invalid",
3226 Self::ZeroEventSequence => "event sequence must be positive",
3227 Self::InvalidSessionCreatedEnvelope => {
3228 "SessionCreated must be sequence 1 with no run_id"
3229 }
3230 Self::MissingRunId => "run-scoped event requires run_id",
3231 })
3232 }
3233}
3234impl std::error::Error for EventSchemaError {}