Skip to main content

agents_kit/
error.rs

1//! Portable, safely formatted failures for Agent operations.
2
3use std::fmt;
4use std::time::Duration;
5
6/// Broad portable failure category. Categories do not establish retry safety.
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub enum ErrorKind {
9    /// Invalid passive configuration.
10    InvalidConfig,
11    /// Invalid caller request.
12    InvalidInput,
13    /// A portable resource was proven absent.
14    NotFound,
15    /// Caller input conflicts with durable facts.
16    Conflict,
17    /// The owning lifecycle is closed.
18    Closed,
19    /// Existing work prevents admission.
20    Busy,
21    /// Accepted work durably reached cancellation.
22    Cancelled,
23    /// A local deadline elapsed.
24    Timeout,
25    /// The selected implementation cannot preserve requested semantics.
26    Unsupported,
27    /// The selected adapter does not yet implement the capability.
28    NotYetImplemented,
29    /// External data contradicts its protocol.
30    Protocol,
31    /// Kit-owned facts contradict their invariants.
32    InvalidState,
33    /// A dependency operation failed.
34    Integration,
35}
36
37/// Caller-visible operation. Accepted streamed and unary work both use `Prompt`.
38#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub enum Operation {
40    /// No operation was supplied by the constructor.
41    Unspecified,
42    /// Construct passive configuration.
43    Configure,
44    /// Validate passive request data.
45    ValidateInput,
46    /// Construct a new Agent.
47    Build,
48    /// Reopen an existing Agent.
49    Reopen,
50    /// List stored Agents.
51    ListAgents,
52    /// Read Agent information.
53    AgentInfo,
54    /// Create a workspace.
55    NewWorkspace,
56    /// List workspaces.
57    ListWorkspaces,
58    /// Read workspace information.
59    WorkspaceInfo,
60    /// Open a workspace handle.
61    GetWorkspace,
62    /// Check workspace update readiness.
63    CheckWorkspaceUpdate,
64    /// Update a workspace.
65    UpdateWorkspace,
66    /// Delete a workspace.
67    DeleteWorkspace,
68    /// List advertised models.
69    ListModels,
70    /// List executable tools.
71    ListTools,
72    /// Ingest managed knowledge.
73    IngestKnowledge,
74    /// Close a workspace handle.
75    CloseWorkspace,
76    /// Create a conversation.
77    NewConversation,
78    /// List conversations.
79    ListConversations,
80    /// Read conversation information.
81    ConversationInfo,
82    /// Open a conversation handle.
83    GetConversation,
84    /// Execute or observe an accepted prompt.
85    Prompt,
86    /// Abort accepted prompt work.
87    Abort,
88    /// Read a workspace-relative file.
89    ReadFile,
90    /// Receive durable notifications.
91    NotificationStream,
92    /// Resolve a pending interaction.
93    ResolveNotification,
94    /// Close a conversation.
95    CloseConversation,
96    /// Shut down an Agent.
97    Shutdown,
98}
99
100/// Semantic step at which the failure was detected.
101#[derive(Clone, Copy, Debug, Eq, PartialEq)]
102pub enum ErrorPhase {
103    /// The detecting step was not specified.
104    Unspecified,
105    /// Passive configuration validation.
106    Configuration,
107    /// Request validation.
108    InputValidation,
109    /// Operation admission.
110    Admission,
111    /// Client construction.
112    ClientSetup,
113    /// Process startup.
114    ProcessStart,
115    /// Server health and protocol verification.
116    ServerVerification,
117    /// Model or tool inventory verification.
118    InventoryVerification,
119    /// Waiting for the event owner to become ready.
120    EventReady,
121    /// Event synchronization.
122    EventSynchronization,
123    /// Selecting the configured model.
124    ModelSelection,
125    /// Model generation.
126    ModelGeneration,
127    /// Executable tool work.
128    ToolExecution,
129    /// Validating a completed model turn.
130    CompletionValidation,
131    /// Reading managed knowledge.
132    KnowledgeRead,
133    /// Writing managed knowledge.
134    KnowledgeWrite,
135    /// Building a native request.
136    RequestBuild,
137    /// Sending a native request.
138    RequestSend,
139    /// Reading a response.
140    ResponseRead,
141    /// Decoding a response.
142    ResponseDecode,
143    /// Reading control-plane state.
144    StateRead,
145    /// Writing control-plane state.
146    StateWrite,
147    /// Committing control-plane state.
148    StateCommit,
149    /// Reconciling existing work.
150    Reconciliation,
151    /// Resolving an interaction.
152    InteractionResolution,
153    /// Cancelling accepted work.
154    Cancellation,
155    /// Publishing an outcome to observers.
156    Publication,
157    /// Settling an owned operation.
158    Settlement,
159    /// Releasing owned resources.
160    Cleanup,
161    /// Lifecycle admission or closure.
162    Lifecycle,
163}
164
165/// Safe operation and detection context.
166///
167/// ```
168/// use agents_kit::error::{ErrorContext, ErrorPhase, Operation};
169/// let context = ErrorContext { operation: Operation::Prompt, phase: ErrorPhase::ModelGeneration };
170/// assert_eq!(context.operation, Operation::Prompt);
171/// ```
172#[derive(Clone, Copy, Debug, Eq, PartialEq)]
173pub struct ErrorContext {
174    /// Caller-visible operation.
175    pub operation: Operation,
176    /// Detecting semantic step.
177    pub phase: ErrorPhase,
178}
179
180/// The owner of a local deadline, without implying that side effects stopped.
181#[derive(Clone, Copy, Debug, Eq, PartialEq)]
182pub enum DeadlineKind {
183    /// Construction, shutdown or another lifecycle bound.
184    Lifecycle,
185    /// One dependency request.
186    Request,
187    /// One logical operation.
188    Operation,
189    /// Waiting for an independently owned operation.
190    Observation,
191}
192
193/// Specific sourced evidence, independent of the broad recovery category.
194#[derive(Clone, Copy, Debug, Eq, PartialEq)]
195pub enum ErrorDetail {
196    /// Authentication was rejected.
197    AuthenticationRejected,
198    /// Permission was denied.
199    PermissionDenied,
200    /// A remote request was rejected as invalid.
201    InvalidRequest,
202    /// A dependency resource was absent.
203    ResourceMissing,
204    /// A remote service reported conflict.
205    RemoteConflict,
206    /// A request exceeded an accepted size.
207    RequestTooLarge,
208    /// A remote precondition failed.
209    PreconditionFailed,
210    /// Resources were exhausted without a more precise classification.
211    ResourceExhausted,
212    /// An authoritative rate limit was reported.
213    RateLimited,
214    /// An authoritative quota limit was reported.
215    QuotaExceeded,
216    /// Billing requirements prevented execution.
217    BillingRequired,
218    /// A service was unavailable.
219    ServiceUnavailable,
220    /// A remote internal failure was reported.
221    RemoteInternal,
222    /// A remote service reported cancellation, not Agent cancellation.
223    RemoteCancelled,
224    /// A remote service reported its deadline elapsed.
225    RemoteDeadlineExceeded,
226    /// A remote rejection had no narrower supported classification.
227    RemoteRejected,
228    /// Dependency transport failed.
229    TransportFailure,
230    /// A dependency was closed, not necessarily the Agent.
231    DependencyClosed,
232    /// A dependency cancelled its work, not necessarily the Agent prompt.
233    DependencyCancelled,
234    /// An owned task terminated unexpectedly.
235    TaskTerminated,
236    /// Generation reached its output limit.
237    OutputLimit,
238    /// Generation exceeded a context limit.
239    ContextLimit,
240    /// A session budget was exhausted.
241    SessionLimit,
242    /// Generated content was filtered.
243    ContentFiltered,
244    /// A policy rejected execution.
245    PolicyRejected,
246    /// Provider-managed continuation is required.
247    ContinuationRequired,
248    /// Received data could not be decoded.
249    MalformedData,
250    /// Required response data was absent.
251    MissingData,
252    /// Received data had an invalid value or shape.
253    InvalidData,
254    /// An unknown protocol discriminator was received.
255    UnknownDiscriminator,
256    /// Response correlation did not match its owner.
257    CorrelationMismatch,
258    /// Protocol steps or fields contradicted each other.
259    InvalidSequence,
260    /// A stream ended without its required terminal.
261    IncompleteStream,
262    /// Output did not satisfy the requested schema.
263    StructuredOutputMismatch,
264    /// A completed tool result violated its output contract.
265    InvalidToolOutput,
266    /// A supplied revision was stale.
267    StaleRevision,
268    /// Reopened attachments disagreed with durable configuration.
269    AttachmentMismatch,
270    /// The StateStore reported an exact transaction conflict.
271    TransactionConflict,
272    /// Non-idempotent acceptance could not be established.
273    AcceptanceUnknown,
274    /// Exclusive runtime ownership could not be established.
275    OwnershipConflict,
276    /// A response exceeded a known configured limit.
277    ResponseLimit {
278        /// Maximum accepted size or count.
279        limit: usize,
280    },
281    /// A local deadline actually elapsed.
282    DeadlineElapsed {
283        /// The owner of the deadline.
284        kind: DeadlineKind,
285        /// Configured duration when known, never inferred from elapsed time.
286        timeout: Option<Duration>,
287    },
288}
289
290/// An opaque, safely formatted failure with bounded normalized evidence.
291///
292/// Messages supplied by callers must already be redacted. Never include native
293/// identities, routes, credentials, storage keys, payloads or foreign prose.
294///
295/// ```
296/// use agents_kit::{KitError, error::{ErrorKind, Operation, ErrorPhase, ErrorDetail}};
297/// let error = KitError::integration("model authentication was rejected")
298///     .with_context(Operation::Prompt, ErrorPhase::ModelGeneration)
299///     .with_detail(ErrorDetail::AuthenticationRejected)
300///     .with_http_status(401);
301/// assert_eq!(error.kind(), ErrorKind::Integration);
302/// assert_eq!(error.http_status(), Some(401));
303/// ```
304///
305/// Runtime failures cannot be reconstructed from untrusted serialized messages.
306/// ```compile_fail
307/// let error = agents_kit::KitError::integration("safe failure");
308/// let _ = serde_json::to_string(&error);
309/// ```
310#[derive(Clone)]
311pub struct KitError {
312    data: Box<ErrorData>,
313}
314
315#[derive(Clone)]
316struct ErrorData {
317    kind: ErrorKind,
318    context: ErrorContext,
319    message: String,
320    detail: Option<ErrorDetail>,
321    http_status: Option<u16>,
322    cause: Option<KitError>,
323    supplementary: Vec<KitError>,
324    truncated: bool,
325}
326
327impl KitError {
328    /// Constructs a classified failure with already-redacted caller context.
329    ///
330    /// ```
331    /// use agents_kit::KitError;
332    /// use agents_kit::error::{ErrorKind};
333    /// let error = KitError::new(ErrorKind::Integration, "safe dependency failure");
334    /// assert_eq!(error.kind(), ErrorKind::Integration);
335    /// ```
336    pub fn new(kind: ErrorKind, safe_message: impl Into<String>) -> Self {
337        let mut error = Self {
338            data: Box::new(ErrorData {
339                kind,
340                context: ErrorContext {
341                    operation: Operation::Unspecified,
342                    phase: ErrorPhase::Unspecified,
343                },
344                message: safe_message.into(),
345                detail: None,
346                http_status: None,
347                cause: None,
348                supplementary: Vec::new(),
349                truncated: false,
350            }),
351        };
352        error.bound();
353        error
354    }
355
356    /// Creates an invalid-configuration failure with safe context.
357    ///
358    /// ```
359    /// use agents_kit::KitError;
360    /// use agents_kit::error::{ErrorKind};
361    /// let error = KitError::invalid_config("safe failure context");
362    /// assert_eq!(error.kind(), ErrorKind::InvalidConfig);
363    /// ```
364    pub fn invalid_config(message: impl Into<String>) -> Self {
365        Self::new(ErrorKind::InvalidConfig, message)
366            .with_context(Operation::Configure, ErrorPhase::Configuration)
367    }
368    /// Creates an invalid-input failure with safe context.
369    ///
370    /// ```
371    /// use agents_kit::KitError;
372    /// use agents_kit::error::{ErrorKind};
373    /// let error = KitError::invalid_input("safe failure context");
374    /// assert_eq!(error.kind(), ErrorKind::InvalidInput);
375    /// ```
376    pub fn invalid_input(message: impl Into<String>) -> Self {
377        Self::new(ErrorKind::InvalidInput, message)
378            .with_context(Operation::ValidateInput, ErrorPhase::InputValidation)
379    }
380    /// Creates a proven missing-portable-resource failure.
381    ///
382    /// ```
383    /// use agents_kit::KitError;
384    /// use agents_kit::error::{ErrorKind};
385    /// let error = KitError::not_found("safe failure context");
386    /// assert_eq!(error.kind(), ErrorKind::NotFound);
387    /// ```
388    pub fn not_found(message: impl Into<String>) -> Self {
389        Self::new(ErrorKind::NotFound, message)
390    }
391    /// Creates a durable-fact conflict.
392    ///
393    /// ```
394    /// use agents_kit::KitError;
395    /// use agents_kit::error::{ErrorKind};
396    /// let error = KitError::conflict("safe failure context");
397    /// assert_eq!(error.kind(), ErrorKind::Conflict);
398    /// ```
399    pub fn conflict(message: impl Into<String>) -> Self {
400        Self::new(ErrorKind::Conflict, message)
401    }
402    /// Creates an owning-lifecycle closure failure.
403    ///
404    /// ```
405    /// use agents_kit::KitError;
406    /// use agents_kit::error::{ErrorKind};
407    /// let error = KitError::closed("safe failure context");
408    /// assert_eq!(error.kind(), ErrorKind::Closed);
409    /// ```
410    pub fn closed(message: impl Into<String>) -> Self {
411        Self::new(ErrorKind::Closed, message).at_phase(ErrorPhase::Lifecycle)
412    }
413    /// Creates a busy-admission failure.
414    ///
415    /// ```
416    /// use agents_kit::KitError;
417    /// use agents_kit::error::{ErrorKind};
418    /// let error = KitError::busy("safe failure context");
419    /// assert_eq!(error.kind(), ErrorKind::Busy);
420    /// ```
421    pub fn busy(message: impl Into<String>) -> Self {
422        Self::new(ErrorKind::Busy, message).at_phase(ErrorPhase::Admission)
423    }
424    /// Creates a durable Agent cancellation failure.
425    ///
426    /// ```
427    /// use agents_kit::KitError;
428    /// use agents_kit::error::{ErrorKind};
429    /// let error = KitError::cancelled("safe failure context");
430    /// assert_eq!(error.kind(), ErrorKind::Cancelled);
431    /// ```
432    pub fn cancelled(message: impl Into<String>) -> Self {
433        Self::new(ErrorKind::Cancelled, message).at_phase(ErrorPhase::Cancellation)
434    }
435    /// Creates a local deadline failure; attach the known owner and duration.
436    ///
437    /// ```
438    /// use agents_kit::KitError;
439    /// use agents_kit::error::{ErrorKind};
440    /// let error = KitError::timeout("safe failure context");
441    /// assert_eq!(error.kind(), ErrorKind::Timeout);
442    /// ```
443    pub fn timeout(message: impl Into<String>) -> Self {
444        Self::new(ErrorKind::Timeout, message).with_detail(ErrorDetail::DeadlineElapsed {
445            kind: DeadlineKind::Operation,
446            timeout: None,
447        })
448    }
449    /// Creates an unsupported-semantics failure.
450    ///
451    /// ```
452    /// use agents_kit::KitError;
453    /// use agents_kit::error::{ErrorKind};
454    /// let error = KitError::unsupported("safe failure context");
455    /// assert_eq!(error.kind(), ErrorKind::Unsupported);
456    /// ```
457    pub fn unsupported(message: impl Into<String>) -> Self {
458        Self::new(ErrorKind::Unsupported, message)
459    }
460    /// Creates an incomplete-adapter failure.
461    ///
462    /// ```
463    /// use agents_kit::KitError;
464    /// use agents_kit::error::{ErrorKind};
465    /// let error = KitError::not_yet_implemented("safe failure context");
466    /// assert_eq!(error.kind(), ErrorKind::NotYetImplemented);
467    /// ```
468    pub fn not_yet_implemented(message: impl Into<String>) -> Self {
469        Self::new(ErrorKind::NotYetImplemented, message)
470    }
471    /// Creates a malformed or contradictory protocol failure.
472    ///
473    /// ```
474    /// use agents_kit::KitError;
475    /// use agents_kit::error::{ErrorKind};
476    /// let error = KitError::protocol("safe failure context");
477    /// assert_eq!(error.kind(), ErrorKind::Protocol);
478    /// ```
479    pub fn protocol(message: impl Into<String>) -> Self {
480        Self::new(ErrorKind::Protocol, message)
481    }
482    /// Creates an invalid-owned-state failure.
483    ///
484    /// ```
485    /// use agents_kit::KitError;
486    /// use agents_kit::error::{ErrorKind};
487    /// let error = KitError::invalid_state("safe failure context");
488    /// assert_eq!(error.kind(), ErrorKind::InvalidState);
489    /// ```
490    pub fn invalid_state(message: impl Into<String>) -> Self {
491        Self::new(ErrorKind::InvalidState, message)
492    }
493    /// Creates a dependency execution failure.
494    ///
495    /// ```
496    /// use agents_kit::KitError;
497    /// use agents_kit::error::{ErrorKind};
498    /// let error = KitError::integration("safe failure context");
499    /// assert_eq!(error.kind(), ErrorKind::Integration);
500    /// ```
501    pub fn integration(message: impl Into<String>) -> Self {
502        Self::new(ErrorKind::Integration, message)
503    }
504    /// Attaches the caller operation and detecting phase without changing evidence.
505    ///
506    /// ```
507    /// use agents_kit::KitError;
508    /// use agents_kit::error::{Operation, ErrorPhase};
509    /// let error = KitError::protocol("missing reply").with_context(Operation::Prompt, ErrorPhase::ResponseDecode);
510    /// assert_eq!(error.context().phase, ErrorPhase::ResponseDecode);
511    /// ```
512    pub fn with_context(mut self, operation: Operation, phase: ErrorPhase) -> Self {
513        self.data.context = ErrorContext { operation, phase };
514        self
515    }
516    /// Attaches sourced portable evidence without changing the broad category.
517    ///
518    /// ```
519    /// use agents_kit::KitError;
520    /// use agents_kit::error::{ErrorDetail};
521    /// let error = KitError::protocol("missing reply").with_detail(ErrorDetail::MissingData);
522    /// assert_eq!(error.detail(), Some(&ErrorDetail::MissingData));
523    /// ```
524    pub fn with_detail(mut self, detail: ErrorDetail) -> Self {
525        self.data.detail = Some(detail);
526        self
527    }
528    /// Attaches an observed HTTP status, never an inferred status.
529    ///
530    /// ```
531    /// use agents_kit::KitError;
532    /// let error = KitError::integration("rejected request").with_http_status(403);
533    /// assert_eq!(error.http_status(), Some(403));
534    /// ```
535    pub fn with_http_status(mut self, status: u16) -> Self {
536        self.data.http_status = Some(status);
537        self
538    }
539    /// Attaches a normalized causal failure, separately from cleanup failures.
540    ///
541    /// ```
542    /// use agents_kit::KitError;
543    /// use agents_kit::error::{ErrorKind};
544    /// let error = KitError::integration("generation failed").with_cause(KitError::timeout("dependency deadline elapsed"));
545    /// assert_eq!(error.cause().unwrap().kind(), ErrorKind::Timeout);
546    /// ```
547    pub fn with_cause(mut self, cause: Self) -> Self {
548        self.data.cause = Some(cause);
549        self.bound();
550        self
551    }
552    /// Appends independent cleanup or earlier-attempt evidence in observation order.
553    ///
554    /// ```
555    /// use agents_kit::KitError;
556    /// let error = KitError::protocol("invalid reply").with_supplementary(KitError::integration("cleanup failed"));
557    /// assert_eq!(error.supplementary().len(), 1);
558    /// ```
559    pub fn with_supplementary(mut self, error: Self) -> Self {
560        self.data.supplementary.push(error);
561        self.bound();
562        self
563    }
564    /// Returns the broad portable category.
565    ///
566    /// ```
567    /// use agents_kit::KitError;
568    /// use agents_kit::error::{ErrorKind};
569    /// assert_eq!(KitError::closed("agent shut down").kind(), ErrorKind::Closed);
570    /// ```
571    pub fn kind(&self) -> ErrorKind {
572        self.data.kind
573    }
574    /// Returns the caller operation and detecting phase.
575    ///
576    /// ```
577    /// use agents_kit::KitError;
578    /// use agents_kit::error::{Operation, ErrorPhase};
579    /// let error = KitError::protocol("invalid reply").with_context(Operation::Prompt, ErrorPhase::ResponseDecode);
580    /// assert_eq!(error.context().operation, Operation::Prompt);
581    /// ```
582    pub fn context(&self) -> &ErrorContext {
583        &self.data.context
584    }
585    /// Returns narrower evidence when a detector established it.
586    ///
587    /// ```
588    /// use agents_kit::KitError;
589    /// use agents_kit::error::{ErrorDetail};
590    /// let error = KitError::integration("output truncated").with_detail(ErrorDetail::OutputLimit);
591    /// assert_eq!(error.detail(), Some(&ErrorDetail::OutputLimit));
592    /// ```
593    pub fn detail(&self) -> Option<&ErrorDetail> {
594        self.data.detail.as_ref()
595    }
596    /// Returns the actual observed HTTP status.
597    ///
598    /// ```
599    /// use agents_kit::KitError;
600    /// assert_eq!(KitError::integration("no HTTP response").http_status(), None);
601    /// ```
602    pub fn http_status(&self) -> Option<u16> {
603        self.data.http_status
604    }
605    /// Returns the normalized causal failure.
606    ///
607    /// ```
608    /// use agents_kit::KitError;
609    /// assert!(KitError::invalid_input("empty prompt").cause().is_none());
610    /// ```
611    pub fn cause(&self) -> Option<&Self> {
612        self.data.cause.as_ref()
613    }
614    /// Returns independent supplementary failures in observation order.
615    ///
616    /// ```
617    /// use agents_kit::KitError;
618    /// assert!(KitError::invalid_input("empty prompt").supplementary().is_empty());
619    /// ```
620    pub fn supplementary(&self) -> &[Self] {
621        &self.data.supplementary
622    }
623    /// Reports whether any retained diagnostic tree evidence was truncated.
624    ///
625    /// ```
626    /// use agents_kit::KitError;
627    /// let error = KitError::integration("x".repeat(5000));
628    /// assert!(error.diagnostics_truncated());
629    /// assert_eq!(error.message().len(), 4096);
630    /// ```
631    pub fn diagnostics_truncated(&self) -> bool {
632        self.data.truncated
633    }
634    /// Returns bounded, already-redacted caller context.
635    ///
636    /// ```
637    /// use agents_kit::KitError;
638    /// let error = KitError::invalid_input("prompt must be nonempty");
639    /// assert_eq!(error.message(), "prompt must be nonempty");
640    /// ```
641    pub fn message(&self) -> &str {
642        &self.data.message
643    }
644
645    pub(crate) fn at_operation(mut self, operation: Operation) -> Self {
646        self.data.context.operation = operation;
647        self.inherit_operation(operation);
648        self
649    }
650    fn inherit_operation(&mut self, operation: Operation) {
651        if self.data.context.operation == Operation::Unspecified {
652            self.data.context.operation = operation;
653        }
654        if let Some(cause) = &mut self.data.cause {
655            cause.inherit_operation(operation);
656        }
657        for error in &mut self.data.supplementary {
658            error.inherit_operation(operation);
659        }
660    }
661    pub(crate) fn at_phase(mut self, phase: ErrorPhase) -> Self {
662        self.data.context.phase = phase;
663        self
664    }
665    pub(crate) fn in_phase(self, phase: ErrorPhase) -> Self {
666        if self.context().phase == ErrorPhase::Unspecified {
667            self.at_phase(phase)
668        } else {
669            self
670        }
671    }
672    pub(crate) fn with_diagnostics_truncated(mut self) -> Self {
673        self.data.truncated = true;
674        self
675    }
676    pub(crate) fn with_cleanup_result(self, cleanup: Result<(), Self>) -> Self {
677        match cleanup {
678            Ok(()) => self,
679            Err(error) => self.with_supplementary(error.in_phase(ErrorPhase::Cleanup)),
680        }
681    }
682
683    pub(crate) fn record_cleanup(primary: &mut Option<Self>, error: Self) {
684        let error = error.in_phase(ErrorPhase::Cleanup);
685        *primary = Some(match primary.take() {
686            Some(primary) => primary.with_supplementary(error),
687            None => error,
688        });
689    }
690
691    pub(crate) fn from_state_store(
692        source: storage_kit::StateStoreError,
693        message: &'static str,
694    ) -> Self {
695        use storage_kit::StateStoreError;
696        match source {
697            StateStoreError::TransactionConflict { .. } => {
698                Self::conflict(message).with_detail(ErrorDetail::TransactionConflict)
699            }
700            StateStoreError::TimedOut { timeout_ms, .. } => {
701                Self::timeout(message).with_detail(ErrorDetail::DeadlineElapsed {
702                    kind: DeadlineKind::Operation,
703                    timeout: Some(Duration::from_millis(timeout_ms)),
704                })
705            }
706            StateStoreError::Unavailable { .. } => {
707                Self::integration(message).with_detail(ErrorDetail::ServiceUnavailable)
708            }
709            StateStoreError::InvalidConfig(_) | StateStoreError::InvalidRequest(_) => {
710                Self::integration(message).with_detail(ErrorDetail::InvalidData)
711            }
712            StateStoreError::Serialization(_) => {
713                Self::integration(message).with_detail(ErrorDetail::MalformedData)
714            }
715            StateStoreError::BackendOperation { .. } => Self::integration(message),
716        }
717    }
718    pub(crate) fn from_provider(source: &providers_kit::KitError) -> Self {
719        use providers_kit::error::{
720            ErrorKind as ProviderKind, ErrorPhase as ProviderPhase, ResponseViolation,
721        };
722        let (kind, detail) = match source.kind() {
723            ProviderKind::Configuration => {
724                (ErrorKind::InvalidConfig, Some(ErrorDetail::InvalidData))
725            }
726            ProviderKind::InvalidInput => (ErrorKind::InvalidInput, Some(ErrorDetail::InvalidData)),
727            ProviderKind::Unsupported => (ErrorKind::Unsupported, None),
728            ProviderKind::Transport => {
729                (ErrorKind::Integration, Some(ErrorDetail::TransportFailure))
730            }
731            ProviderKind::Timeout => (
732                ErrorKind::Timeout,
733                Some(ErrorDetail::DeadlineElapsed {
734                    kind: DeadlineKind::Request,
735                    timeout: source.timeout(),
736                }),
737            ),
738            ProviderKind::Closed => (ErrorKind::Integration, Some(ErrorDetail::DependencyClosed)),
739            ProviderKind::InvalidState => (ErrorKind::InvalidState, Some(ErrorDetail::InvalidData)),
740            ProviderKind::InvalidResponse(violation) => (
741                ErrorKind::Protocol,
742                Some(match violation {
743                    ResponseViolation::MalformedJson | ResponseViolation::InvalidUtf8 => {
744                        ErrorDetail::MalformedData
745                    }
746                    ResponseViolation::MissingField { .. } => ErrorDetail::MissingData,
747                    ResponseViolation::InvalidField { .. } => ErrorDetail::InvalidData,
748                    ResponseViolation::UnknownVariant { .. } => ErrorDetail::UnknownDiscriminator,
749                    ResponseViolation::IdentityMismatch { .. } => ErrorDetail::CorrelationMismatch,
750                    ResponseViolation::ProtocolViolation { .. } => ErrorDetail::InvalidSequence,
751                    ResponseViolation::UnexpectedEnd => ErrorDetail::IncompleteStream,
752                    ResponseViolation::LimitExceeded { limit } => {
753                        ErrorDetail::ResponseLimit { limit }
754                    }
755                }),
756            ),
757            ProviderKind::InvalidRequest => {
758                (ErrorKind::Integration, Some(ErrorDetail::InvalidRequest))
759            }
760            ProviderKind::Authentication => (
761                ErrorKind::Integration,
762                Some(ErrorDetail::AuthenticationRejected),
763            ),
764            ProviderKind::PermissionDenied => {
765                (ErrorKind::Integration, Some(ErrorDetail::PermissionDenied))
766            }
767            ProviderKind::NotFound => (ErrorKind::Integration, Some(ErrorDetail::ResourceMissing)),
768            ProviderKind::Conflict => (ErrorKind::Integration, Some(ErrorDetail::RemoteConflict)),
769            ProviderKind::RequestTooLarge => {
770                (ErrorKind::Integration, Some(ErrorDetail::RequestTooLarge))
771            }
772            ProviderKind::FailedPrecondition => (
773                ErrorKind::Integration,
774                Some(ErrorDetail::PreconditionFailed),
775            ),
776            ProviderKind::ResourceExhausted => {
777                (ErrorKind::Integration, Some(ErrorDetail::ResourceExhausted))
778            }
779            ProviderKind::RateLimited => (ErrorKind::Integration, Some(ErrorDetail::RateLimited)),
780            ProviderKind::QuotaExceeded => {
781                (ErrorKind::Integration, Some(ErrorDetail::QuotaExceeded))
782            }
783            ProviderKind::Billing => (ErrorKind::Integration, Some(ErrorDetail::BillingRequired)),
784            ProviderKind::ServiceUnavailable => (
785                ErrorKind::Integration,
786                Some(ErrorDetail::ServiceUnavailable),
787            ),
788            ProviderKind::Internal => (ErrorKind::Integration, Some(ErrorDetail::RemoteInternal)),
789            ProviderKind::Cancelled => (ErrorKind::Integration, Some(ErrorDetail::RemoteCancelled)),
790            ProviderKind::DeadlineExceeded => (
791                ErrorKind::Integration,
792                Some(ErrorDetail::RemoteDeadlineExceeded),
793            ),
794            ProviderKind::ProviderRejected => {
795                (ErrorKind::Integration, Some(ErrorDetail::RemoteRejected))
796            }
797        };
798        let phase = match source.context().phase {
799            ProviderPhase::Unspecified => ErrorPhase::Unspecified,
800            ProviderPhase::Configuration => ErrorPhase::Configuration,
801            ProviderPhase::InputValidation => ErrorPhase::InputValidation,
802            ProviderPhase::RequestBuild => ErrorPhase::RequestBuild,
803            ProviderPhase::RequestSend | ProviderPhase::SessionSend => ErrorPhase::RequestSend,
804            ProviderPhase::ResponseHeaders
805            | ProviderPhase::ReadBody
806            | ProviderPhase::Stream
807            | ProviderPhase::SessionReceive => ErrorPhase::ResponseRead,
808            ProviderPhase::Decode => ErrorPhase::ResponseDecode,
809            ProviderPhase::Normalize => ErrorPhase::CompletionValidation,
810            ProviderPhase::SessionHandshake => ErrorPhase::ClientSetup,
811            ProviderPhase::Lifecycle | ProviderPhase::SessionClose => ErrorPhase::Lifecycle,
812            ProviderPhase::Cleanup => ErrorPhase::Cleanup,
813        };
814        let mut error = Self::new(kind, "provider operation failed").at_phase(phase);
815        if let Some(detail) = detail {
816            error = error.with_detail(detail);
817        }
818        if let Some(status) = source.http_status() {
819            error = error.with_http_status(status);
820        }
821        if let Some(cause) = source.cause() {
822            error = error.with_cause(Self::from_provider(cause));
823        }
824        for supplementary in source.supplementary() {
825            error = error.with_supplementary(Self::from_provider(supplementary));
826        }
827        if source.diagnostics_truncated() {
828            error = error.with_diagnostics_truncated();
829        }
830        error
831    }
832    fn bound(&mut self) {
833        self.bound_tree(1, &mut 32, &mut (64 * 1024));
834    }
835    fn bound_tree(&mut self, depth: usize, nodes: &mut usize, bytes: &mut usize) {
836        *nodes -= 1;
837        let limit = (*bytes).min(4096);
838        if self.data.message.len() > limit {
839            let mut boundary = limit;
840            while !self.data.message.is_char_boundary(boundary) {
841                boundary -= 1;
842            }
843            self.data.message.truncate(boundary);
844            self.data.truncated = true;
845        }
846        *bytes -= self.data.message.len();
847        if depth == 8 || *nodes == 0 {
848            self.data.truncated |= self.data.cause.is_some() || !self.data.supplementary.is_empty();
849            self.data.cause = None;
850            self.data.supplementary.clear();
851            return;
852        }
853        if let Some(cause) = &mut self.data.cause {
854            cause.bound_tree(depth + 1, nodes, bytes);
855            self.data.truncated |= cause.diagnostics_truncated();
856        }
857        let mut retained = 0;
858        for error in &mut self.data.supplementary {
859            if *nodes == 0 {
860                break;
861            }
862            error.bound_tree(depth + 1, nodes, bytes);
863            self.data.truncated |= error.diagnostics_truncated();
864            retained += 1;
865        }
866        self.data.truncated |= retained < self.data.supplementary.len();
867        self.data.supplementary.truncate(retained);
868    }
869}
870
871impl fmt::Display for KitError {
872    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
873        write!(
874            f,
875            "{:?} during {:?}/{:?}",
876            self.kind(),
877            self.context().operation,
878            self.context().phase
879        )?;
880        if let Some(detail) = self.detail() {
881            write!(f, ": {detail:?}")?;
882        }
883        if !self.message().is_empty() {
884            write!(f, ": {}", self.message())?;
885        }
886        if let Some(status) = self.http_status() {
887            write!(f, " (HTTP {status})")?;
888        }
889        if !self.supplementary().is_empty() {
890            write!(
891                f,
892                " ({} supplementary failures)",
893                self.supplementary().len()
894            )?;
895        }
896        if self.diagnostics_truncated() {
897            f.write_str(" (diagnostics truncated)")?;
898        }
899        Ok(())
900    }
901}
902
903impl fmt::Debug for KitError {
904    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
905        f.debug_struct("KitError")
906            .field("kind", &self.kind())
907            .field("context", self.context())
908            .field("detail", &self.detail())
909            .field("message", &self.message())
910            .field("http_status", &self.http_status())
911            .field("cause", &self.cause())
912            .field("supplementary", &self.supplementary())
913            .field("diagnostics_truncated", &self.diagnostics_truncated())
914            .finish()
915    }
916}
917
918impl std::error::Error for KitError {
919    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
920        self.cause()
921            .map(|cause| cause as &(dyn std::error::Error + 'static))
922    }
923}
924
925impl From<tools_kit::ToolError> for KitError {
926    fn from(source: tools_kit::ToolError) -> Self {
927        use tools_kit::ToolError;
928        let error = match source {
929            ToolError::Timeout { timeout_secs, .. } => Self::timeout(
930                "tool operation deadline elapsed",
931            )
932            .with_detail(ErrorDetail::DeadlineElapsed {
933                kind: DeadlineKind::Request,
934                timeout: Some(Duration::from_secs(timeout_secs)),
935            }),
936            ToolError::Closed { .. } => Self::integration("tool runtime is closed")
937                .with_detail(ErrorDetail::DependencyClosed),
938            ToolError::Cancelled { .. } => Self::integration("tool operation was cancelled")
939                .with_detail(ErrorDetail::DependencyCancelled),
940            ToolError::InvalidOutput { .. } => Self::integration("tool output is invalid")
941                .with_detail(ErrorDetail::InvalidToolOutput),
942            ToolError::Protocol { .. } => Self::protocol("tool protocol data is invalid")
943                .with_detail(ErrorDetail::MalformedData),
944            ToolError::Transport { .. } => Self::integration("tool transport failed")
945                .with_detail(ErrorDetail::TransportFailure),
946            ToolError::UnknownTool { .. } => Self::integration("selected tool is absent")
947                .with_detail(ErrorDetail::ResourceMissing),
948            ToolError::InvalidConfiguration { .. } | ToolError::DuplicateName { .. } => {
949                Self::integration("tool configuration is invalid")
950                    .with_detail(ErrorDetail::InvalidData)
951            }
952            ToolError::InvalidArguments { .. }
953            | ToolError::InvalidInteraction { .. }
954            | ToolError::UndeclaredInteraction { .. } => {
955                Self::integration("tool input is invalid").with_detail(ErrorDetail::InvalidData)
956            }
957            ToolError::InteractionDeclined { .. } => {
958                Self::integration("tool interaction was declined")
959                    .with_detail(ErrorDetail::PermissionDenied)
960            }
961            ToolError::Unsupported { .. } => Self::unsupported("tool operation is unsupported"),
962            ToolError::BuildCleanup { primary, cleanup } => Self::from(*primary)
963                .with_supplementary(Self::from(*cleanup).at_phase(ErrorPhase::Cleanup)),
964        };
965        error.in_phase(ErrorPhase::ToolExecution)
966    }
967}
968
969impl From<reqwest::Error> for KitError {
970    fn from(source: reqwest::Error) -> Self {
971        if source.is_timeout() {
972            Self::timeout("HTTP request deadline elapsed")
973                .with_detail(ErrorDetail::DeadlineElapsed {
974                    kind: DeadlineKind::Request,
975                    timeout: None,
976                })
977                .at_phase(ErrorPhase::RequestSend)
978        } else {
979            let error = Self::integration("HTTP operation failed")
980                .with_detail(ErrorDetail::TransportFailure);
981            match source.status() {
982                Some(status) => error.with_http_status(status.as_u16()),
983                None => error,
984            }
985        }
986    }
987}
988
989impl From<std::io::Error> for KitError {
990    fn from(source: std::io::Error) -> Self {
991        match source.kind() {
992            std::io::ErrorKind::TimedOut => Self::timeout("IO operation deadline elapsed"),
993            std::io::ErrorKind::PermissionDenied => Self::integration("IO permission was denied")
994                .with_detail(ErrorDetail::PermissionDenied),
995            std::io::ErrorKind::NotFound => Self::integration("IO resource was absent")
996                .with_detail(ErrorDetail::ResourceMissing),
997            _ => {
998                Self::integration("IO operation failed").with_detail(ErrorDetail::TransportFailure)
999            }
1000        }
1001    }
1002}