use super::CodexErrorInfo; use super::ThreadItem; use super::ThreadStatus; use super::TurnStatus; use codex_protocol::protocol::SessionSource as CoreSessionSource; use codex_protocol::protocol::SubAgentSource as CoreSubAgentSource; use codex_protocol::protocol::ThreadSource as CoreThreadSource; use codex_utils_absolute_path::AbsolutePathBuf; use schemars::JsonSchema; use schemars::r#gen::SchemaGenerator; use schemars::schema::Schema; use serde::Deserialize; use serde::Serialize; use std::path::PathBuf; use thiserror::Error; use ts_rs::TS; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(rename_all = "camelCase", export_to = "v2/")] #[derive(Default)] pub enum SessionSource { Cli, #[serde(rename = "vscode")] #[ts(rename = "vscode")] #[default] VsCode, Exec, AppServer, Custom(String), SubAgent(CoreSubAgentSource), #[serde(other)] Unknown, } impl From for SessionSource { fn from(value: CoreSessionSource) -> Self { match value { CoreSessionSource::Cli => SessionSource::Cli, CoreSessionSource::VSCode => SessionSource::VsCode, CoreSessionSource::Exec => SessionSource::Exec, CoreSessionSource::Mcp => SessionSource::AppServer, CoreSessionSource::Custom(source) => SessionSource::Custom(source), // We do not want to render those at the app-server level. CoreSessionSource::Internal(_) => SessionSource::Unknown, CoreSessionSource::SubAgent(sub) => SessionSource::SubAgent(sub), CoreSessionSource::Unknown => SessionSource::Unknown, } } } impl From for CoreSessionSource { fn from(value: SessionSource) -> Self { match value { SessionSource::Cli => CoreSessionSource::Cli, SessionSource::VsCode => CoreSessionSource::VSCode, SessionSource::Exec => CoreSessionSource::Exec, SessionSource::AppServer => CoreSessionSource::Mcp, SessionSource::Custom(source) => CoreSessionSource::Custom(source), SessionSource::SubAgent(sub) => CoreSessionSource::SubAgent(sub), SessionSource::Unknown => CoreSessionSource::Unknown, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, TS)] #[serde(try_from = "String", into = "String")] #[ts(type = "string")] #[ts(export_to = "v2/")] pub enum ThreadSource { User, Subagent, Feature(String), MemoryConsolidation, } impl JsonSchema for ThreadSource { fn schema_name() -> String { "ThreadSource".to_string() } fn json_schema(generator: &mut SchemaGenerator) -> Schema { String::json_schema(generator) } } impl TryFrom for ThreadSource { type Error = String; fn try_from(value: String) -> Result { value.parse::().map(Into::into) } } impl From for String { fn from(value: ThreadSource) -> Self { CoreThreadSource::from(value).into() } } impl From for ThreadSource { fn from(value: CoreThreadSource) -> Self { match value { CoreThreadSource::User => ThreadSource::User, CoreThreadSource::Subagent => ThreadSource::Subagent, CoreThreadSource::Feature(feature) => ThreadSource::Feature(feature), CoreThreadSource::MemoryConsolidation => ThreadSource::MemoryConsolidation, } } } impl From for CoreThreadSource { fn from(value: ThreadSource) -> Self { match value { ThreadSource::User => CoreThreadSource::User, ThreadSource::Subagent => CoreThreadSource::Subagent, ThreadSource::Feature(feature) => CoreThreadSource::Feature(feature), ThreadSource::MemoryConsolidation => CoreThreadSource::MemoryConsolidation, } } } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct GitInfo { pub sha: Option, pub branch: Option, pub origin_url: Option, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct Thread { pub id: String, /// Session id shared by threads that belong to the same session tree. pub session_id: String, /// Source thread id when this thread was created by forking another thread. pub forked_from_id: Option, /// The ID of the parent thread. This will only be set if this thread is a subagent. pub parent_thread_id: Option, /// Usually the first user message in the thread, if available. pub preview: String, /// Whether the thread is ephemeral and should not be materialized on disk. pub ephemeral: bool, /// Model provider used for this thread (for example, 'openai'). pub model_provider: String, /// Unix timestamp (in seconds) when the thread was created. #[ts(type = "number")] pub created_at: i64, /// Unix timestamp (in seconds) when the thread was last updated. #[ts(type = "number")] pub updated_at: i64, /// Unix timestamp (in seconds) used for thread recency ordering. #[ts(type = "number | null")] pub recency_at: Option, /// Current runtime status for the thread. pub status: ThreadStatus, /// [UNSTABLE] Path to the thread on disk. pub path: Option, /// Working directory captured for the thread. pub cwd: AbsolutePathBuf, /// Version of the CLI that created the thread. pub cli_version: String, /// Origin of the thread (CLI, VSCode, codex exec, codex app-server, etc.). pub source: SessionSource, /// Optional analytics source classification for this thread. pub thread_source: Option, /// Optional random unique nickname assigned to an AgentControl-spawned sub-agent. pub agent_nickname: Option, /// Optional role (agent_role) assigned to an AgentControl-spawned sub-agent. pub agent_role: Option, /// Optional Git metadata captured when the thread was created. pub git_info: Option, /// Optional user-facing thread title. pub name: Option, /// Only populated on `thread/resume`, `thread/rollback`, `thread/fork`, and `thread/read` /// (when `includeTurns` is true) responses. /// For all other responses and notifications returning a Thread, /// the turns field will be an empty list. pub turns: Vec, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct Turn { pub id: String, /// Thread items currently included in this turn payload. pub items: Vec, /// Describes how much of `items` has been loaded for this turn. #[serde(default)] pub items_view: TurnItemsView, pub status: TurnStatus, /// Only populated when the Turn's status is failed. pub error: Option, /// Unix timestamp (in seconds) when the turn started. #[ts(type = "number | null")] pub started_at: Option, /// Unix timestamp (in seconds) when the turn completed. #[ts(type = "number | null")] pub completed_at: Option, /// Duration between turn start and completion in milliseconds, if known. #[ts(type = "number | null")] pub duration_ms: Option, } #[derive(Default, Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub enum TurnItemsView { /// `items` was not loaded for this turn. The field is intentionally empty. NotLoaded, /// `items` contains only a display summary for this turn. Summary, /// `items` contains every ThreadItem available from persisted app-server history for this turn. #[default] Full, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, Error)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] #[error("{message}")] pub struct TurnError { pub message: String, pub codex_error_info: Option, #[serde(default)] pub additional_details: Option, }