1use llm::{ContentBlock, LlmCallPurpose, LlmError, MessageId, ModelIdentity, StopReason, TokenUsage};
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
7#[serde(tag = "status", rename_all = "snake_case")]
8pub enum TurnOutcome {
9 Completed,
10 Cancelled,
11 Failed { message_id: MessageId, error: String },
12}
13
14impl TurnOutcome {
15 pub fn failed(error: impl Into<String>) -> Self {
16 Self::Failed { message_id: MessageId::new(), error: error.into() }
17 }
18}
19
20#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
22#[serde(tag = "status", rename_all = "snake_case")]
23pub enum LlmCallOutcome {
24 Completed {
25 stop_reason: Option<StopReason>,
26 usage: Option<TokenUsage>,
27 },
28 Failed {
29 error: String,
30 will_retry: bool,
31 #[serde(default, skip_serializing_if = "Option::is_none")]
32 http_status: Option<u16>,
33 #[serde(default, skip_serializing_if = "Option::is_none")]
34 provider_request_id: Option<String>,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
36 provider_error_code: Option<String>,
37 },
38 Cancelled,
39}
40
41impl LlmCallOutcome {
42 pub fn failed(error: impl Into<String>, will_retry: bool) -> Self {
43 Self::Failed {
44 error: error.into(),
45 will_retry,
46 http_status: None,
47 provider_request_id: None,
48 provider_error_code: None,
49 }
50 }
51
52 pub fn from_llm_error(error: &LlmError, will_retry: bool) -> Self {
53 let Some(provider) = error.provider() else {
54 return Self::failed(error.to_string(), will_retry);
55 };
56 Self::Failed {
57 error: provider.to_string(),
58 will_retry,
59 http_status: provider.http_status,
60 provider_request_id: provider.request_id.clone(),
61 provider_error_code: provider.code.clone(),
62 }
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct RetryInfo {
69 pub attempt: u32,
70 pub max_attempts: u32,
71 pub delay_ms: u64,
72}
73
74#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
83#[serde(tag = "type", rename_all = "snake_case")]
84pub enum TurnEvent {
85 Started {
88 #[serde(default, skip_serializing_if = "Vec::is_empty")]
89 content: Vec<ContentBlock>,
90 },
91 RetryScheduled { purpose: LlmCallPurpose, attempt: u32, max_attempts: u32, delay_ms: u64 },
93 LlmCallStarted {
95 purpose: LlmCallPurpose,
96 model: ModelIdentity,
97 display_name: String,
98 attempt: u32,
100 max_attempts: u32,
101 },
102 LlmCallEnded { purpose: LlmCallPurpose, outcome: LlmCallOutcome },
104 AutoContinue { attempt: u32, max_attempts: u32, message_id: MessageId, content: Vec<ContentBlock> },
107 Ended { outcome: TurnOutcome },
109}
110
111impl TurnEvent {
112 pub fn retry_info(&self) -> Option<RetryInfo> {
113 match self {
114 Self::RetryScheduled { attempt, max_attempts, delay_ms, .. } => {
115 Some(RetryInfo { attempt: *attempt, max_attempts: *max_attempts, delay_ms: *delay_ms })
116 }
117 _ => None,
118 }
119 }
120}