mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
add turn items view to app-server turns (#21063)
## Why `Turn.items` currently overloads an empty array to mean either that no items exist or that the server intentionally did not load them for this response. That ambiguity blocks future lazy-loading work where clients need to distinguish unloaded, summary, and fully hydrated turn payloads. ## What changed - add a new `TurnItemsView` enum with `notLoaded`, `summary`, and `full` variants - add required `itemsView` metadata to app-server `Turn` payloads - mark reconstructed persisted history as `full` and live shell-style turn payloads as `notLoaded` - keep current `thread/turns/list` behavior unchanged and document that it still returns `full` turns today - regenerate the JSON and TypeScript protocol fixtures ## Verification - `just write-app-server-schema` - `cargo test -p codex-app-server-protocol` - `cargo test -p codex-app-server thread_read_can_include_turns` - `cargo test -p codex-app-server thread_turns_list_can_page_backward_and_forward` - `cargo test -p codex-app-server thread_resume_rejects_history_when_thread_is_running` - `just fix -p codex-app-server-protocol` - `just fix -p codex-app-server` - `just fmt`
This commit is contained in:
committed by
GitHub
Unverified
parent
b6d4c4ea6b
commit
9e0c191c13
@@ -17,6 +17,7 @@ use crate::protocol::v2::ThreadItem;
|
||||
use crate::protocol::v2::Turn;
|
||||
use crate::protocol::v2::TurnError as V2TurnError;
|
||||
use crate::protocol::v2::TurnError;
|
||||
use crate::protocol::v2::TurnItemsView;
|
||||
use crate::protocol::v2::TurnStatus;
|
||||
use crate::protocol::v2::UserInput;
|
||||
use crate::protocol::v2::WebSearchAction;
|
||||
@@ -1166,6 +1167,7 @@ impl From<PendingTurn> for Turn {
|
||||
Self {
|
||||
id: value.id,
|
||||
items: value.items,
|
||||
items_view: TurnItemsView::Full,
|
||||
error: value.error,
|
||||
status: value.status,
|
||||
started_at: value.started_at,
|
||||
@@ -1180,6 +1182,7 @@ impl From<&PendingTurn> for Turn {
|
||||
Self {
|
||||
id: value.id.clone(),
|
||||
items: value.items.clone(),
|
||||
items_view: TurnItemsView::Full,
|
||||
error: value.error.clone(),
|
||||
status: value.status.clone(),
|
||||
started_at: value.started_at,
|
||||
@@ -1453,6 +1456,7 @@ mod tests {
|
||||
started_at: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
items_view: TurnItemsView::Full,
|
||||
items: vec![
|
||||
ThreadItem::UserMessage {
|
||||
id: "item-1".into(),
|
||||
@@ -2723,6 +2727,7 @@ mod tests {
|
||||
started_at: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
items_view: TurnItemsView::Full,
|
||||
items: Vec::new(),
|
||||
}]
|
||||
);
|
||||
@@ -2982,6 +2987,7 @@ mod tests {
|
||||
started_at: None,
|
||||
completed_at: None,
|
||||
duration_ms: None,
|
||||
items_view: TurnItemsView::Full,
|
||||
items: vec![ThreadItem::UserMessage {
|
||||
id: "item-1".into(),
|
||||
content: vec![UserInput::Text {
|
||||
|
||||
@@ -5434,10 +5434,11 @@ impl From<CoreTokenUsage> for TokenUsageBreakdown {
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct Turn {
|
||||
pub id: String,
|
||||
/// Only populated on a `thread/resume` or `thread/fork` response.
|
||||
/// For all other responses and notifications returning a Turn,
|
||||
/// the items field will be an empty list.
|
||||
/// 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>,
|
||||
@@ -5452,6 +5453,19 @@ pub struct Turn {
|
||||
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, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
@@ -8441,6 +8455,22 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn turn_defaults_legacy_missing_items_view_to_full() {
|
||||
let turn: Turn = serde_json::from_value(json!({
|
||||
"id": "turn_123",
|
||||
"items": [],
|
||||
"status": "completed",
|
||||
"error": null,
|
||||
"startedAt": null,
|
||||
"completedAt": null,
|
||||
"durationMs": null,
|
||||
}))
|
||||
.expect("legacy turn should deserialize");
|
||||
|
||||
assert_eq!(turn.items_view, TurnItemsView::Full);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_list_params_accepts_single_cwd() {
|
||||
let params = serde_json::from_value::<ThreadListParams>(json!({
|
||||
|
||||
Reference in New Issue
Block a user