Skip to main content

agent_first_data/
protocol.rs

1use serde::Serialize;
2use serde_json::Value;
3use std::fmt;
4
5// ═══════════════════════════════════════════
6// Event Type and Build Errors (0.16 API)
7// ═══════════════════════════════════════════
8
9/// A typed, strict-valid AFDATA protocol v1 event.
10///
11/// Wraps the complete JSON envelope and provides access to the underlying value.
12/// Events produced by builders are guaranteed to pass `validate_protocol_event(_, true)`.
13#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
14pub struct Event(Value);
15
16impl Event {
17    /// Access the underlying JSON value.
18    pub fn as_value(&self) -> &Value {
19        &self.0
20    }
21
22    /// Convert into the underlying JSON value.
23    pub fn into_value(self) -> Value {
24        self.0
25    }
26}
27
28impl From<Event> for Value {
29    fn from(event: Event) -> Self {
30        event.0
31    }
32}
33
34impl fmt::Display for Event {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        write!(f, "{}", self.0)
37    }
38}
39
40/// Error type for builder failures.
41///
42/// Errors occur when:
43/// - A reserved error field is overwritten (code, message, hint, retryable)
44/// - An object field (.fields or .extend) is not a JSON object
45/// - A required field (code/message for convenience functions) is empty
46#[derive(Clone, Debug, PartialEq, Eq)]
47pub enum BuildError {
48    ReservedField(String),
49    NonObjectField(String),
50    EmptyRequiredField(String),
51}
52
53impl fmt::Display for BuildError {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        match self {
56            Self::ReservedField(msg) => write!(f, "reserved field: {msg}"),
57            Self::NonObjectField(msg) => write!(f, "non-object field: {msg}"),
58            Self::EmptyRequiredField(msg) => write!(f, "empty required field: {msg}"),
59        }
60    }
61}
62
63impl std::error::Error for BuildError {}
64
65/// Log level enumeration (serialized as lowercase).
66#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
67#[serde(rename_all = "lowercase")]
68pub enum LogLevel {
69    Debug,
70    Info,
71    Warn,
72    Error,
73}
74
75impl LogLevel {
76    pub(crate) fn as_str(&self) -> &'static str {
77        match self {
78            Self::Debug => "debug",
79            Self::Info => "info",
80            Self::Warn => "warn",
81            Self::Error => "error",
82        }
83    }
84}
85
86// ═══════════════════════════════════════════
87// Result Builder
88// ═══════════════════════════════════════════
89
90/// Builder for result events.
91pub struct ResultBuilder {
92    payload: Value,
93    trace: Option<Value>,
94}
95
96impl ResultBuilder {
97    /// Set the trace object.
98    pub fn trace(mut self, trace: Value) -> Self {
99        self.trace = Some(trace);
100        self
101    }
102
103    /// Build the event. This builder cannot fail.
104    pub fn build(self) -> Event {
105        let trace = self
106            .trace
107            .unwrap_or_else(|| Value::Object(serde_json::Map::new()));
108        let mut obj = serde_json::Map::new();
109        obj.insert("kind".to_string(), Value::String("result".to_string()));
110        obj.insert("result".to_string(), self.payload);
111        obj.insert("trace".to_string(), trace);
112        Event(Value::Object(obj))
113    }
114}
115
116/// Fluent builder: start building a result event.
117pub fn json_result(payload: Value) -> ResultBuilder {
118    ResultBuilder {
119        payload,
120        trace: None,
121    }
122}
123
124// ═══════════════════════════════════════════
125// Error Builder
126// ═══════════════════════════════════════════
127
128/// Builder for error events.
129pub struct ErrorBuilder {
130    code: String,
131    message: String,
132    retryable: bool,
133    hint: Option<String>,
134    fields: serde_json::Map<String, Value>,
135    trace: Option<Value>,
136    build_error: Option<BuildError>,
137}
138
139impl ErrorBuilder {
140    /// Mark the error as retryable.
141    pub fn retryable(mut self) -> Self {
142        self.retryable = true;
143        self
144    }
145
146    /// Set retryable based on a boolean condition.
147    pub fn retryable_if(mut self, should_retry: bool) -> Self {
148        self.retryable = should_retry;
149        self
150    }
151
152    /// Set the hint.
153    pub fn hint(mut self, hint: &str) -> Self {
154        self.hint = Some(hint.to_string());
155        self
156    }
157
158    /// Set the hint if present.
159    pub fn hint_if_some(mut self, hint: Option<&str>) -> Self {
160        if let Some(h) = hint {
161            self.hint = Some(h.to_string());
162        }
163        self
164    }
165
166    /// Add an extension field.
167    pub fn field(mut self, name: &str, value: Value) -> Self {
168        if self.build_error.is_none() {
169            match name {
170                "code" | "message" | "hint" | "retryable" => {
171                    self.build_error = Some(BuildError::ReservedField(format!(
172                        "cannot write reserved field {name:?} to error payload"
173                    )));
174                }
175                _ => {
176                    self.fields.insert(name.to_string(), value);
177                }
178            }
179        }
180        self
181    }
182
183    /// Add multiple extension fields from a JSON object.
184    pub fn fields(mut self, fields: Value) -> Self {
185        if self.build_error.is_none() {
186            match fields {
187                Value::Object(map) => {
188                    for (k, v) in map {
189                        match k.as_str() {
190                            "code" | "message" | "hint" | "retryable" => {
191                                self.build_error = Some(BuildError::ReservedField(format!(
192                                    "cannot write reserved field {k:?} to error payload"
193                                )));
194                                return self;
195                            }
196                            _ => {
197                                self.fields.insert(k, v);
198                            }
199                        }
200                    }
201                }
202                _ => {
203                    self.build_error = Some(BuildError::NonObjectField(
204                        "fields() argument must be a JSON object".to_string(),
205                    ));
206                }
207            }
208        }
209        self
210    }
211
212    /// Extend with a serializable value (must serialize to JSON object).
213    pub fn extend<T: Serialize>(mut self, value: T) -> Self {
214        if self.build_error.is_none() {
215            match serde_json::to_value(&value) {
216                Ok(Value::Object(map)) => {
217                    for (k, v) in map {
218                        match k.as_str() {
219                            "code" | "message" | "hint" | "retryable" => {
220                                self.build_error = Some(BuildError::ReservedField(format!(
221                                    "cannot write reserved field {k:?} to error payload"
222                                )));
223                                return self;
224                            }
225                            _ => {
226                                self.fields.insert(k, v);
227                            }
228                        }
229                    }
230                }
231                Ok(_) => {
232                    self.build_error = Some(BuildError::NonObjectField(
233                        "extend() argument must serialize to a JSON object".to_string(),
234                    ));
235                }
236                Err(_) => {
237                    self.build_error = Some(BuildError::NonObjectField(
238                        "extend() argument serialization failed".to_string(),
239                    ));
240                }
241            }
242        }
243        self
244    }
245
246    /// Set the trace object.
247    pub fn trace(mut self, trace: Value) -> Self {
248        self.trace = Some(trace);
249        self
250    }
251
252    /// Build the event.
253    pub fn build(self) -> Result<Event, BuildError> {
254        if let Some(err) = self.build_error {
255            return Err(err);
256        }
257
258        let mut error_obj = self.fields;
259        error_obj.insert("code".to_string(), Value::String(self.code));
260        error_obj.insert("message".to_string(), Value::String(self.message));
261        error_obj.insert("retryable".to_string(), Value::Bool(self.retryable));
262        if let Some(h) = self.hint {
263            error_obj.insert("hint".to_string(), Value::String(h));
264        }
265
266        let trace = self
267            .trace
268            .unwrap_or_else(|| Value::Object(serde_json::Map::new()));
269        let mut obj = serde_json::Map::new();
270        obj.insert("kind".to_string(), Value::String("error".to_string()));
271        obj.insert("error".to_string(), Value::Object(error_obj));
272        obj.insert("trace".to_string(), trace);
273
274        Ok(Event(Value::Object(obj)))
275    }
276}
277
278/// Fluent builder: start building an error event.
279///
280/// An empty `code` or `message` does not panic here; it seeds a deferred
281/// [`BuildError::EmptyRequiredField`] that is surfaced when [`ErrorBuilder::build`]
282/// is called.
283pub fn json_error(code: &str, message: &str) -> ErrorBuilder {
284    let build_error = if code.is_empty() {
285        Some(BuildError::EmptyRequiredField(
286            "error code must not be empty".to_string(),
287        ))
288    } else if message.is_empty() {
289        Some(BuildError::EmptyRequiredField(
290            "error message must not be empty".to_string(),
291        ))
292    } else {
293        None
294    };
295    ErrorBuilder {
296        code: code.to_string(),
297        message: message.to_string(),
298        retryable: false,
299        hint: None,
300        fields: serde_json::Map::new(),
301        trace: None,
302        build_error,
303    }
304}
305
306// ═══════════════════════════════════════════
307// Progress Builder
308// ═══════════════════════════════════════════
309
310/// Builder for progress events.
311pub struct ProgressBuilder {
312    payload: Value,
313    trace: Option<Value>,
314}
315
316impl ProgressBuilder {
317    /// Set the trace object.
318    pub fn trace(mut self, trace: Value) -> Self {
319        self.trace = Some(trace);
320        self
321    }
322
323    /// Build the event. This builder cannot fail.
324    pub fn build(self) -> Event {
325        let trace = self
326            .trace
327            .unwrap_or_else(|| Value::Object(serde_json::Map::new()));
328        let mut obj = serde_json::Map::new();
329        obj.insert("kind".to_string(), Value::String("progress".to_string()));
330        obj.insert("progress".to_string(), self.payload);
331        obj.insert("trace".to_string(), trace);
332
333        Event(Value::Object(obj))
334    }
335}
336
337/// Fluent builder: start building a progress event.
338///
339pub fn json_progress(payload: Value) -> ProgressBuilder {
340    ProgressBuilder {
341        payload,
342        trace: None,
343    }
344}
345
346// ═══════════════════════════════════════════
347// Log Builder
348// ═══════════════════════════════════════════
349
350/// Builder for log events.
351pub struct LogBuilder {
352    payload: Value,
353    trace: Option<Value>,
354}
355
356impl LogBuilder {
357    /// Set the trace object.
358    pub fn trace(mut self, trace: Value) -> Self {
359        self.trace = Some(trace);
360        self
361    }
362
363    /// Build the event. This builder cannot fail.
364    pub fn build(self) -> Event {
365        let trace = self
366            .trace
367            .unwrap_or_else(|| Value::Object(serde_json::Map::new()));
368        let mut obj = serde_json::Map::new();
369        obj.insert("kind".to_string(), Value::String("log".to_string()));
370        obj.insert("log".to_string(), self.payload);
371        obj.insert("trace".to_string(), trace);
372
373        Event(Value::Object(obj))
374    }
375}
376
377/// Fluent builder: start building a log event.
378///
379pub fn json_log(payload: Value) -> LogBuilder {
380    LogBuilder {
381        payload,
382        trace: None,
383    }
384}
385
386// ═══════════════════════════════════════════
387// CLI Helper
388// ═══════════════════════════════════════════
389
390/// Build a CLI error event with optional hint.
391///
392/// Always returns a strict-valid `kind:"error"` event with code `"cli_error"`,
393/// `retryable: false`, and an empty `trace`. An empty `message` is replaced with
394/// a generic placeholder so the returned event stays strict-valid without panicking.
395pub fn build_cli_error(message: &str, hint: Option<&str>) -> Event {
396    let message = if message.is_empty() {
397        "unspecified error"
398    } else {
399        message
400    };
401    match json_error("cli_error", message).hint_if_some(hint).build() {
402        Ok(event) => event,
403        // Unreachable in practice (non-empty code+message, no reserved fields);
404        // construct a minimal valid envelope directly rather than panic.
405        Err(_) => {
406            let mut error_obj = serde_json::Map::new();
407            error_obj.insert("code".to_string(), Value::String("cli_error".to_string()));
408            error_obj.insert(
409                "message".to_string(),
410                Value::String("unspecified error".to_string()),
411            );
412            error_obj.insert("retryable".to_string(), Value::Bool(false));
413            let mut obj = serde_json::Map::new();
414            obj.insert("kind".to_string(), Value::String("error".to_string()));
415            obj.insert("error".to_string(), Value::Object(error_obj));
416            obj.insert("trace".to_string(), Value::Object(serde_json::Map::new()));
417            Event(Value::Object(obj))
418        }
419    }
420}
421
422// ═══════════════════════════════════════════
423// Validation
424// ═══════════════════════════════════════════
425
426/// A single protocol-validation violation: a stable machine-readable `rule`
427/// slug, a JSON pointer to the offending location (`""` = the whole event),
428/// and a human-readable `message`.
429///
430/// The `rule` slugs are a stable contract (the CLI maps them 1:1 onto its
431/// `validate` finding `rule_id`s). The full set:
432/// `event_not_object`, `kind_invalid`, `kind_unsupported`, `payload_missing`,
433/// `unexpected_field`, `trace_not_object`, `trace_required`, `error_not_object`,
434/// `error_code_invalid`, `error_message_invalid`, `error_hint_invalid`,
435/// `error_retryable_invalid`, `stream_non_terminal_after_terminal`,
436/// `stream_duplicate_terminal`, `stream_missing_terminal`.
437#[derive(Clone, Debug, PartialEq, Eq)]
438pub struct ProtocolViolation {
439    pub rule: &'static str,
440    pub pointer: String,
441    pub message: String,
442}
443
444impl std::fmt::Display for ProtocolViolation {
445    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
446        write!(f, "{}", self.message)
447    }
448}
449
450impl std::error::Error for ProtocolViolation {}
451
452fn violation(
453    rule: &'static str,
454    pointer: impl Into<String>,
455    message: impl Into<String>,
456) -> ProtocolViolation {
457    ProtocolViolation {
458        rule,
459        pointer: pointer.into(),
460        message: message.into(),
461    }
462}
463
464/// Validate one protocol v1 event envelope.
465///
466/// `strict` additionally enforces the recommended strict profile: `trace` is
467/// required, and kind-specific payload shapes are checked (see
468/// [`validate_protocol_event_strict_payload`]).
469///
470/// Returns the first [`ProtocolViolation`] found, or `Ok(())`.
471pub fn validate_protocol_event(event: &Value, strict: bool) -> Result<(), ProtocolViolation> {
472    validate_protocol_event_base(event)?;
473    if strict {
474        validate_protocol_event_strict_payload(event)?;
475    }
476    Ok(())
477}
478
479fn validate_protocol_event_base(event: &Value) -> Result<(), ProtocolViolation> {
480    let Some(obj) = event.as_object() else {
481        return Err(violation(
482            "event_not_object",
483            "",
484            "event must be a JSON object",
485        ));
486    };
487    let Some(kind) = obj.get("kind").and_then(Value::as_str) else {
488        return Err(violation(
489            "kind_invalid",
490            "/kind",
491            "event.kind must be one of result, error, progress, log",
492        ));
493    };
494    if !matches!(kind, "result" | "error" | "progress" | "log") {
495        return Err(violation(
496            "kind_unsupported",
497            "/kind",
498            format!("unsupported event kind {kind:?}"),
499        ));
500    }
501    if !obj.contains_key(kind) {
502        return Err(violation(
503            "payload_missing",
504            format!("/{kind}"),
505            format!("event payload field {kind:?} is required"),
506        ));
507    }
508    for key in obj.keys() {
509        if key != "kind" && key != kind && key != "trace" {
510            return Err(violation(
511                "unexpected_field",
512                format!("/{key}"),
513                format!("unexpected top-level field {key:?}"),
514            ));
515        }
516    }
517    if let Some(trace) = obj.get("trace")
518        && !trace.is_object()
519    {
520        return Err(violation(
521            "trace_not_object",
522            "/trace",
523            "event.trace must be a JSON object when present",
524        ));
525    }
526    if kind == "error" {
527        validate_error_payload(obj.get("error"))?;
528    }
529    Ok(())
530}
531
532fn validate_error_payload(error: Option<&Value>) -> Result<(), ProtocolViolation> {
533    let Some(error) = error.and_then(Value::as_object) else {
534        return Err(violation(
535            "error_not_object",
536            "/error",
537            "event.error must be a JSON object",
538        ));
539    };
540    match error.get("code").and_then(Value::as_str) {
541        Some(code) if !code.is_empty() => {}
542        _ => {
543            return Err(violation(
544                "error_code_invalid",
545                "/error/code",
546                "event.error.code must be a non-empty string",
547            ));
548        }
549    }
550    match error.get("message").and_then(Value::as_str) {
551        Some(message) if !message.is_empty() => {}
552        _ => {
553            return Err(violation(
554                "error_message_invalid",
555                "/error/message",
556                "event.error.message must be a non-empty string",
557            ));
558        }
559    }
560    if error.get("hint").is_some_and(|hint| !hint.is_string()) {
561        return Err(violation(
562            "error_hint_invalid",
563            "/error/hint",
564            "event.error.hint must be a string when present",
565        ));
566    }
567    Ok(())
568}
569
570/// Validate a finite structured CLI event stream:
571/// `(log | progress)* -> exactly one (result | error) -> end`.
572///
573/// `strict` is forwarded to [`validate_protocol_event`] for every event.
574///
575/// Unlike [`validate_protocol_event`], this collects every violation across
576/// the whole stream instead of failing fast on the first one; per-event
577/// violation pointers are prefixed with the event's index (`/{idx}{pointer}`).
578pub fn validate_protocol_stream(
579    events: &[Value],
580    strict: bool,
581) -> Result<(), Vec<ProtocolViolation>> {
582    let mut violations = Vec::new();
583    let mut terminal_seen = false;
584    for (idx, event) in events.iter().enumerate() {
585        if let Err(v) = validate_protocol_event(event, strict) {
586            violations.push(ProtocolViolation {
587                rule: v.rule,
588                pointer: format!("/{idx}{}", v.pointer),
589                message: v.message,
590            });
591        }
592        match event.get("kind").and_then(Value::as_str) {
593            Some("log") | Some("progress") => {
594                if terminal_seen {
595                    violations.push(violation(
596                        "stream_non_terminal_after_terminal",
597                        format!("/{idx}"),
598                        "non-terminal event after terminal",
599                    ));
600                }
601            }
602            Some("result") | Some("error") => {
603                if terminal_seen {
604                    violations.push(violation(
605                        "stream_duplicate_terminal",
606                        format!("/{idx}"),
607                        "duplicate terminal event",
608                    ));
609                } else {
610                    terminal_seen = true;
611                }
612            }
613            _ => {} // an invalid/absent kind is already recorded by validate_protocol_event above
614        }
615    }
616    if !terminal_seen {
617        violations.push(violation(
618            "stream_missing_terminal",
619            String::new(),
620            "event stream must contain exactly one terminal result or error",
621        ));
622    }
623    if violations.is_empty() {
624        Ok(())
625    } else {
626        Err(violations)
627    }
628}
629
630/// Validate one protocol v1 event's payload against the recommended strict profile.
631///
632/// Assumes the base envelope shape (see [`validate_protocol_event_base`]) already passed.
633fn validate_protocol_event_strict_payload(event: &Value) -> Result<(), ProtocolViolation> {
634    if !event.get("trace").is_some_and(Value::is_object) {
635        return Err(violation(
636            "trace_required",
637            "/trace",
638            "event.trace is required by the strict profile",
639        ));
640    }
641    match event.get("kind").and_then(Value::as_str) {
642        Some("error") => validate_strict_error_payload(event.get("error")),
643        _ => Ok(()),
644    }
645}
646
647fn validate_strict_error_payload(error: Option<&Value>) -> Result<(), ProtocolViolation> {
648    let Some(error) = error.and_then(Value::as_object) else {
649        return Err(violation(
650            "error_not_object",
651            "/error",
652            "event.error must be a JSON object in the strict profile",
653        ));
654    };
655    require_non_empty_string(
656        error,
657        "code",
658        "error_code_invalid",
659        "/error/code",
660        "event.error",
661    )?;
662    require_non_empty_string(
663        error,
664        "message",
665        "error_message_invalid",
666        "/error/message",
667        "event.error",
668    )?;
669    if error.get("retryable").and_then(Value::as_bool).is_none() {
670        return Err(violation(
671            "error_retryable_invalid",
672            "/error/retryable",
673            "event.error.retryable must be a boolean in the strict profile",
674        ));
675    }
676    if error.contains_key("hint") && !error.get("hint").is_some_and(Value::is_string) {
677        return Err(violation(
678            "error_hint_invalid",
679            "/error/hint",
680            "event.error.hint must be a string when present",
681        ));
682    }
683    Ok(())
684}
685
686fn require_non_empty_string(
687    payload: &serde_json::Map<String, Value>,
688    field: &str,
689    rule: &'static str,
690    pointer: &'static str,
691    path: &str,
692) -> Result<(), ProtocolViolation> {
693    if payload
694        .get(field)
695        .and_then(Value::as_str)
696        .is_some_and(|value| !value.is_empty())
697    {
698        return Ok(());
699    }
700    Err(violation(
701        rule,
702        pointer,
703        format!("{path}.{field} must be a non-empty string in the strict profile"),
704    ))
705}
706
707// ═══════════════════════════════════════════
708// Reader API: decode_protocol_event
709// ═══════════════════════════════════════════
710
711/// A decoded, strict-valid AFDATA protocol v1 event, typed by kind.
712#[derive(Clone, Debug, PartialEq)]
713pub enum DecodedEvent {
714    Result(DecodedResult),
715    Error(DecodedError),
716    Progress(DecodedProgress),
717    Log(DecodedLog),
718}
719
720/// Decoded `kind:"result"` event.
721#[derive(Clone, Debug, PartialEq)]
722pub struct DecodedResult {
723    /// The raw `result` payload value.
724    pub result: Value,
725    /// The raw `trace` object, when present.
726    pub trace: Option<Value>,
727}
728
729/// Decoded `kind:"error"` event.
730#[derive(Clone, Debug, PartialEq)]
731pub struct DecodedError {
732    pub code: String,
733    pub message: String,
734    pub retryable: bool,
735    pub hint: Option<String>,
736    /// Payload keys beyond `code`, `message`, `retryable`, `hint`.
737    pub fields: serde_json::Map<String, Value>,
738    /// The raw `trace` object, when present.
739    pub trace: Option<Value>,
740}
741
742/// Decoded `kind:"progress"` event.
743#[derive(Clone, Debug, PartialEq)]
744pub struct DecodedProgress {
745    /// The raw `progress` payload value.
746    pub progress: Value,
747    /// The raw `trace` object, when present.
748    pub trace: Option<Value>,
749}
750
751/// Decoded `kind:"log"` event.
752#[derive(Clone, Debug, PartialEq)]
753pub struct DecodedLog {
754    /// The raw `log` payload value.
755    pub log: Value,
756    /// The raw `trace` object, when present.
757    pub trace: Option<Value>,
758}
759
760/// Error returned by [`decode_protocol_event`].
761#[derive(Clone, Debug, PartialEq, Eq)]
762pub enum EventDecodeError {
763    /// `text` is not valid JSON.
764    InvalidJson(String),
765    /// The parsed JSON value failed strict protocol validation.
766    InvalidEvent(String),
767}
768
769impl fmt::Display for EventDecodeError {
770    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
771        match self {
772            Self::InvalidJson(err) => write!(f, "invalid JSON: {err}"),
773            Self::InvalidEvent(err) => write!(f, "invalid protocol event: {err}"),
774        }
775    }
776}
777
778impl std::error::Error for EventDecodeError {}
779
780/// Parse one protocol v1 line, strict-validate it, and return a typed decoded event.
781///
782/// `text` is a single JSON text value (one protocol line), not a JSONL stream.
783pub fn decode_protocol_event(text: &str) -> Result<DecodedEvent, EventDecodeError> {
784    let value: Value =
785        serde_json::from_str(text).map_err(|err| EventDecodeError::InvalidJson(err.to_string()))?;
786    validate_protocol_event(&value, true)
787        .map_err(|v| EventDecodeError::InvalidEvent(v.to_string()))?;
788
789    // Strict validation above guarantees the envelope is an object with a
790    // recognized `kind`; the `ok_or_else` fallbacks below are defensive only.
791    let malformed = || {
792        EventDecodeError::InvalidEvent(
793            "event passed strict validation but has an unexpected shape".to_string(),
794        )
795    };
796    let obj = value.as_object().ok_or_else(malformed)?;
797    let trace = obj.get("trace").cloned();
798    match obj.get("kind").and_then(Value::as_str) {
799        Some("result") => Ok(DecodedEvent::Result(DecodedResult {
800            result: obj.get("result").cloned().unwrap_or(Value::Null),
801            trace,
802        })),
803        Some("error") => {
804            let mut fields = obj
805                .get("error")
806                .and_then(Value::as_object)
807                .ok_or_else(malformed)?
808                .clone();
809            let code = fields
810                .remove("code")
811                .and_then(|v| v.as_str().map(str::to_string))
812                .ok_or_else(malformed)?;
813            let message = fields
814                .remove("message")
815                .and_then(|v| v.as_str().map(str::to_string))
816                .ok_or_else(malformed)?;
817            let retryable = fields
818                .remove("retryable")
819                .and_then(|v| v.as_bool())
820                .ok_or_else(malformed)?;
821            let hint = fields
822                .remove("hint")
823                .and_then(|v| v.as_str().map(str::to_string));
824            Ok(DecodedEvent::Error(DecodedError {
825                code,
826                message,
827                retryable,
828                hint,
829                fields,
830                trace,
831            }))
832        }
833        Some("progress") => Ok(DecodedEvent::Progress(DecodedProgress {
834            progress: obj.get("progress").cloned().unwrap_or(Value::Null),
835            trace,
836        })),
837        Some("log") => Ok(DecodedEvent::Log(DecodedLog {
838            log: obj.get("log").cloned().unwrap_or(Value::Null),
839            trace,
840        })),
841        _ => Err(malformed()),
842    }
843}