mirror of
https://github.com/openai/codex.git
synced 2026-10-22 17:58:46 +00:00
## Why `session_id` and `thread_id` are separate identities after #20437, but app-server only surfaced `sessionId` on the `thread/start`, `thread/resume`, and `thread/fork` response envelopes. Other thread-bearing surfaces such as `thread/list`, `thread/read`, `thread/started`, `thread/rollback`, `thread/metadata/update`, and `thread/unarchive` either lacked the grouping key or forced clients to special-case those three responses. Making `sessionId` part of the reusable `Thread` payload gives every v2 API surface one place to expose session-tree identity. ## Mental model 1. thread.sessionId lives on `Thread` 2. It is a view/runtime identity for the current live session tree, not durable stored lineage metadata 3. When app-server has a live loaded thread, it copies the real value from core’s session_configured.session_id 4. When it only has stored/unloaded data, it falls back to thread.sessionId = thread.id ## What changed - Added `sessionId` to the v2 [`Thread`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server-protocol/src/protocol/v2/thread_data.rs#L105-L109). - Removed the duplicate top-level `sessionId` fields from `thread/start`, `thread/resume`, and `thread/fork`; clients should now read `response.thread.sessionId`. - Populated `thread.sessionId` when building live thread responses, replaying loaded threads, and returning stored-thread summaries so the field is present across start, resume, fork, list, read, rollback, metadata-update, unarchive, and `thread/started` paths. See [`load_thread_from_resume_source_or_send_internal`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/src/request_processors/thread_processor.rs#L2824-L2918) and [`thread_from_stored_thread`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/src/request_processors/thread_processor.rs#L3671-L3719). - Preserved the stored-thread fallback: if a thread has not been loaded into a live session tree yet, `thread.sessionId` falls back to `thread.id`; once the thread is live again, the field reports the active session tree root. - Regenerated the JSON/TypeScript schemas and updated the app-server README examples to show [`thread.sessionId`](https://github.com/openai/codex/blob/8fc9e9b4cf81b6f61d432e71f1eb266f6f104b63/codex-rs/app-server/README.md#L306-L310) on the thread object.
197 lines
7.2 KiB
Rust
197 lines
7.2 KiB
Rust
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 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<CoreSessionSource> 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<SessionSource> 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, Copy, PartialEq, Eq, JsonSchema, TS)]
|
|
#[serde(rename_all = "snake_case")]
|
|
#[ts(rename_all = "snake_case", export_to = "v2/")]
|
|
pub enum ThreadSource {
|
|
User,
|
|
Subagent,
|
|
MemoryConsolidation,
|
|
}
|
|
|
|
impl From<CoreThreadSource> for ThreadSource {
|
|
fn from(value: CoreThreadSource) -> Self {
|
|
match value {
|
|
CoreThreadSource::User => ThreadSource::User,
|
|
CoreThreadSource::Subagent => ThreadSource::Subagent,
|
|
CoreThreadSource::MemoryConsolidation => ThreadSource::MemoryConsolidation,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<ThreadSource> for CoreThreadSource {
|
|
fn from(value: ThreadSource) -> Self {
|
|
match value {
|
|
ThreadSource::User => CoreThreadSource::User,
|
|
ThreadSource::Subagent => CoreThreadSource::Subagent,
|
|
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<String>,
|
|
pub branch: Option<String>,
|
|
pub origin_url: Option<String>,
|
|
}
|
|
|
|
#[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<String>,
|
|
/// 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,
|
|
/// Current runtime status for the thread.
|
|
pub status: ThreadStatus,
|
|
/// [UNSTABLE] Path to the thread on disk.
|
|
pub path: Option<PathBuf>,
|
|
/// 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<ThreadSource>,
|
|
/// Optional random unique nickname assigned to an AgentControl-spawned sub-agent.
|
|
pub agent_nickname: Option<String>,
|
|
/// Optional role (agent_role) assigned to an AgentControl-spawned sub-agent.
|
|
pub agent_role: Option<String>,
|
|
/// Optional Git metadata captured when the thread was created.
|
|
pub git_info: Option<GitInfo>,
|
|
/// Optional user-facing thread title.
|
|
pub name: Option<String>,
|
|
/// 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<Turn>,
|
|
}
|
|
|
|
#[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<ThreadItem>,
|
|
/// 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<TurnError>,
|
|
/// Unix timestamp (in seconds) when the turn started.
|
|
#[ts(type = "number | null")]
|
|
pub started_at: Option<i64>,
|
|
/// Unix timestamp (in seconds) when the turn completed.
|
|
#[ts(type = "number | null")]
|
|
pub completed_at: Option<i64>,
|
|
/// Duration between turn start and completion in milliseconds, if known.
|
|
#[ts(type = "number | null")]
|
|
pub duration_ms: Option<i64>,
|
|
}
|
|
|
|
#[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<CodexErrorInfo>,
|
|
#[serde(default)]
|
|
pub additional_details: Option<String>,
|
|
}
|