mirror of
https://github.com/openai/codex.git
synced 2026-06-01 19:02:59 +00:00
feat(app-server, threadstore): Thread pagination APIs and ThreadStore contract (#21566)
## Why The goal of this PR is to align on app-server and `ThreadStore` API updates for paginating through large threads. #### app-server ##### `thread/turns/list` - Updates `thread/turns/list` to support `itemsView?: "notLoaded" | "summary" | "full" | null`, defaulting to `summary`. - Implements the current `thread/turns/list` behavior over the existing persisted rollout-history fallback: - `notLoaded` returns turn envelopes with empty `items`. - `summary` returns the first user message and final assistant message when available. - `full` preserves the existing full item behavior. Note that this method still uses the naive approach of loading the entire rollout file, and returns just the filtered slice of the data. Real pagination will come later by leveraging SQLite. ##### `thread/turns/items/list` - Adds the experimental `thread/turns/items/list` protocol, schema, dispatcher, and processor stub. The app-server currently returns JSON-RPC `-32601` with `thread/turns/items/list is not supported yet`. #### ThreadStore - Adds the experimental `thread/turns/items/list` protocol, schema, dispatcher, and processor stub. The app-server currently returns JSON-RPC `-32601` with `thread/turns/items/list is not supported yet`. - Adds `ThreadStore` contract types and stubbed methods for listing thread turns and listing items within a turn. - Adds a typed `StoredTurnStatus` and `StoredTurnError` to avoid baking app-server API enums or lossy string status values into the store-facing turn contract. - Adds a typed `StoredTurnStatus` and `StoredTurnError` to avoid baking app-server API enums or lossy string status values into the store-facing turn contract. This also sketches the storage abstraction we expect to need once turns are indexed/stored. In particular, `notLoaded` is useful only if ThreadStore can eventually list turn metadata without loading every persisted item for each turn. ## Validation - Added/updated protocol serialization coverage for the new request and response shapes. - Added app-server integration coverage for `thread/turns/list` default summary behavior and all three `itemsView` modes. - Added app-server integration coverage that `thread/turns/items/list` returns the expected unsupported JSON-RPC error when experimental APIs are enabled. - Added thread-store coverage that the default trait methods return `ThreadStoreError::Unsupported`. No developers.openai.com documentation update is needed for this internal experimental app-server API surface.
This commit is contained in:
@@ -95,6 +95,59 @@ fn turn_defaults_legacy_missing_items_view_to_full() {
|
||||
assert_eq!(turn.items_view, TurnItemsView::Full);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_turns_list_params_accepts_items_view() {
|
||||
let params = serde_json::from_value::<ThreadTurnsListParams>(json!({
|
||||
"threadId": "thr_123",
|
||||
"cursor": null,
|
||||
"limit": 25,
|
||||
"sortDirection": "desc",
|
||||
"itemsView": "notLoaded",
|
||||
}))
|
||||
.expect("thread turns list params should deserialize");
|
||||
|
||||
assert_eq!(params.thread_id, "thr_123");
|
||||
assert_eq!(params.items_view, Some(TurnItemsView::NotLoaded));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_turns_items_list_round_trips() {
|
||||
let params = ThreadTurnsItemsListParams {
|
||||
thread_id: "thr_123".to_string(),
|
||||
turn_id: "turn_456".to_string(),
|
||||
cursor: Some("cursor_1".to_string()),
|
||||
limit: Some(50),
|
||||
sort_direction: Some(SortDirection::Asc),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(¶ms).expect("serialize params"),
|
||||
json!({
|
||||
"threadId": "thr_123",
|
||||
"turnId": "turn_456",
|
||||
"cursor": "cursor_1",
|
||||
"limit": 50,
|
||||
"sortDirection": "asc",
|
||||
})
|
||||
);
|
||||
let response = ThreadTurnsItemsListResponse {
|
||||
data: vec![ThreadItem::ContextCompaction {
|
||||
id: "item_1".to_string(),
|
||||
}],
|
||||
next_cursor: None,
|
||||
backwards_cursor: Some("cursor_0".to_string()),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(&response).expect("serialize response"),
|
||||
json!({
|
||||
"data": [{"type": "contextCompaction", "id": "item_1"}],
|
||||
"nextCursor": null,
|
||||
"backwardsCursor": "cursor_0",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thread_list_params_accepts_single_cwd() {
|
||||
let params = serde_json::from_value::<ThreadListParams>(json!({
|
||||
|
||||
@@ -6,9 +6,11 @@ use super::PermissionProfileSelectionParams;
|
||||
use super::SandboxMode;
|
||||
use super::SandboxPolicy;
|
||||
use super::Thread;
|
||||
use super::ThreadItem;
|
||||
use super::ThreadSource;
|
||||
use super::Turn;
|
||||
use super::TurnEnvironmentParams;
|
||||
use super::TurnItemsView;
|
||||
use super::shared::v2_enum_from_core;
|
||||
use codex_experimental_api_macros::ExperimentalApi;
|
||||
use codex_protocol::config_types::Personality;
|
||||
@@ -1005,6 +1007,9 @@ pub struct ThreadTurnsListParams {
|
||||
/// Optional turn pagination direction; defaults to descending.
|
||||
#[ts(optional = nullable)]
|
||||
pub sort_direction: Option<SortDirection>,
|
||||
/// How much item detail to include for each returned turn; defaults to summary.
|
||||
#[ts(optional = nullable)]
|
||||
pub items_view: Option<TurnItemsView>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
@@ -1022,6 +1027,36 @@ pub struct ThreadTurnsListResponse {
|
||||
pub backwards_cursor: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadTurnsItemsListParams {
|
||||
pub thread_id: String,
|
||||
pub turn_id: String,
|
||||
/// Opaque cursor to pass to the next call to continue after the last item.
|
||||
#[ts(optional = nullable)]
|
||||
pub cursor: Option<String>,
|
||||
/// Optional item page size.
|
||||
#[ts(optional = nullable)]
|
||||
pub limit: Option<u32>,
|
||||
/// Optional item pagination direction; defaults to ascending.
|
||||
#[ts(optional = nullable)]
|
||||
pub sort_direction: Option<SortDirection>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct ThreadTurnsItemsListResponse {
|
||||
pub data: Vec<ThreadItem>,
|
||||
/// Opaque cursor to pass to the next call to continue after the last item.
|
||||
/// if None, there are no more items to return.
|
||||
pub next_cursor: Option<String>,
|
||||
/// Opaque cursor to pass as `cursor` when reversing `sortDirection`.
|
||||
/// This is only populated when the page contains at least one item.
|
||||
pub backwards_cursor: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
|
||||
Reference in New Issue
Block a user