mirror of
https://github.com/openai/codex.git
synced 2026-10-20 00:18:49 +00:00
Deprecate full-history hydration for paginated threads (#40676)
## Why Paginated thread history should be loaded incrementally through `thread/turns/list` and `thread/items/list` instead of being fully reconstructed in `thread.turns`. ## What changed - Emit `deprecationNotice` when `thread/read`, `thread/resume`, or `thread/fork` requests full history for a paginated thread. - Direct clients to omit `includeTurns` for reads or use `excludeTurns: true` for resumes and forks, then page history through the list APIs. - Document the deprecation in the protocol schemas and app-server README. ## Testing - Cover notices for paginated reads, cold and loaded resumes, and forks. - Verify metadata-only requests, legacy threads, and rejected forks do not emit the notice. GitOrigin-RevId: 0d387b46d73608f90b7960120a54af4ef332e510
This commit is contained in:
+3
-3
@@ -4119,7 +4119,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"excludeTurns": {
|
||||
"description": "When true, return only thread metadata and live fork state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after forking.",
|
||||
"description": "When true, return only thread metadata and live fork state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after forking. Full-history hydration is deprecated for paginated threads; use this with `thread/turns/list` and `thread/items/list` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"lastTurnId": {
|
||||
@@ -4505,7 +4505,7 @@
|
||||
"ThreadReadParams": {
|
||||
"properties": {
|
||||
"includeTurns": {
|
||||
"description": "When true, include turns and their items from rollout history.",
|
||||
"description": "When true, include turns and their items from rollout history. Full-history hydration is deprecated for paginated threads; prefer a metadata-only read and page with `thread/turns/list` and `thread/items/list`.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"threadId": {
|
||||
@@ -4720,7 +4720,7 @@
|
||||
]
|
||||
},
|
||||
"excludeTurns": {
|
||||
"description": "When true, return only thread metadata and live-resume state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after resuming.",
|
||||
"description": "When true, return only thread metadata and live-resume state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after resuming. Full-history hydration is deprecated for paginated threads; use this with `thread/turns/list` and `thread/items/list` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"model": {
|
||||
|
||||
+3
-3
@@ -20164,7 +20164,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"excludeTurns": {
|
||||
"description": "When true, return only thread metadata and live fork state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after forking.",
|
||||
"description": "When true, return only thread metadata and live fork state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after forking. Full-history hydration is deprecated for paginated threads; use this with `thread/turns/list` and `thread/items/list` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"lastTurnId": {
|
||||
@@ -21739,7 +21739,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"includeTurns": {
|
||||
"description": "When true, include turns and their items from rollout history.",
|
||||
"description": "When true, include turns and their items from rollout history. Full-history hydration is deprecated for paginated threads; prefer a metadata-only read and page with `thread/turns/list` and `thread/items/list`.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"threadId": {
|
||||
@@ -22366,7 +22366,7 @@
|
||||
]
|
||||
},
|
||||
"excludeTurns": {
|
||||
"description": "When true, return only thread metadata and live-resume state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after resuming.",
|
||||
"description": "When true, return only thread metadata and live-resume state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after resuming. Full-history hydration is deprecated for paginated threads; use this with `thread/turns/list` and `thread/items/list` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"model": {
|
||||
|
||||
+3
-3
@@ -17899,7 +17899,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"excludeTurns": {
|
||||
"description": "When true, return only thread metadata and live fork state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after forking.",
|
||||
"description": "When true, return only thread metadata and live fork state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after forking. Full-history hydration is deprecated for paginated threads; use this with `thread/turns/list` and `thread/items/list` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"lastTurnId": {
|
||||
@@ -19474,7 +19474,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"includeTurns": {
|
||||
"description": "When true, include turns and their items from rollout history.",
|
||||
"description": "When true, include turns and their items from rollout history. Full-history hydration is deprecated for paginated threads; prefer a metadata-only read and page with `thread/turns/list` and `thread/items/list`.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"threadId": {
|
||||
@@ -20101,7 +20101,7 @@
|
||||
]
|
||||
},
|
||||
"excludeTurns": {
|
||||
"description": "When true, return only thread metadata and live-resume state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after resuming.",
|
||||
"description": "When true, return only thread metadata and live-resume state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after resuming. Full-history hydration is deprecated for paginated threads; use this with `thread/turns/list` and `thread/items/list` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"model": {
|
||||
|
||||
@@ -127,7 +127,7 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"excludeTurns": {
|
||||
"description": "When true, return only thread metadata and live fork state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after forking.",
|
||||
"description": "When true, return only thread metadata and live fork state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after forking. Full-history hydration is deprecated for paginated threads; use this with `thread/turns/list` and `thread/items/list` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"lastTurnId": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
"includeTurns": {
|
||||
"description": "When true, include turns and their items from rollout history.",
|
||||
"description": "When true, include turns and their items from rollout history. Full-history hydration is deprecated for paginated threads; prefer a metadata-only read and page with `thread/turns/list` and `thread/items/list`.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"threadId": {
|
||||
|
||||
@@ -1439,7 +1439,7 @@
|
||||
]
|
||||
},
|
||||
"excludeTurns": {
|
||||
"description": "When true, return only thread metadata and live-resume state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after resuming.",
|
||||
"description": "When true, return only thread metadata and live-resume state without populating `thread.turns`. This is useful when the client plans to call `thread/turns/list` immediately after resuming. Full-history hydration is deprecated for paginated threads; use this with `thread/turns/list` and `thread/items/list` instead.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"model": {
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -36,6 +36,8 @@ approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, conf
|
||||
threadSource?: ThreadSource | null, /**
|
||||
* When true, return only thread metadata and live fork state without
|
||||
* populating `thread.turns`. This is useful when the client plans to call
|
||||
* `thread/turns/list` immediately after forking.
|
||||
* `thread/turns/list` immediately after forking. Full-history hydration
|
||||
* is deprecated for paginated threads; use this with `thread/turns/list`
|
||||
* and `thread/items/list` instead.
|
||||
*/
|
||||
excludeTurns?: boolean};
|
||||
|
||||
@@ -5,5 +5,8 @@
|
||||
export type ThreadReadParams = { threadId: string,
|
||||
/**
|
||||
* When true, include turns and their items from rollout history.
|
||||
* Full-history hydration is deprecated for paginated threads; prefer a
|
||||
* metadata-only read and page with `thread/turns/list` and
|
||||
* `thread/items/list`.
|
||||
*/
|
||||
includeTurns?: boolean, };
|
||||
|
||||
@@ -33,6 +33,8 @@ model?: string | null, modelProvider?: string | null, serviceTier?: string | nul
|
||||
approvalsReviewer?: ApprovalsReviewer | null, sandbox?: SandboxMode | null, config?: { [key in string]?: JsonValue } | null, baseInstructions?: string | null, developerInstructions?: string | null, personality?: Personality | null, /**
|
||||
* When true, return only thread metadata and live-resume state without
|
||||
* populating `thread.turns`. This is useful when the client plans to call
|
||||
* `thread/turns/list` immediately after resuming.
|
||||
* `thread/turns/list` immediately after resuming. Full-history hydration
|
||||
* is deprecated for paginated threads; use this with `thread/turns/list`
|
||||
* and `thread/items/list` instead.
|
||||
*/
|
||||
excludeTurns?: boolean};
|
||||
|
||||
@@ -397,7 +397,9 @@ pub struct ThreadResumeParams {
|
||||
pub personality: Option<Personality>,
|
||||
/// When true, return only thread metadata and live-resume state without
|
||||
/// populating `thread.turns`. This is useful when the client plans to call
|
||||
/// `thread/turns/list` immediately after resuming.
|
||||
/// `thread/turns/list` immediately after resuming. Full-history hydration
|
||||
/// is deprecated for paginated threads; use this with `thread/turns/list`
|
||||
/// and `thread/items/list` instead.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub exclude_turns: bool,
|
||||
/// When present, include a `thread/turns/list` page in the resume response
|
||||
@@ -585,7 +587,9 @@ pub struct ThreadForkParams {
|
||||
pub thread_source: Option<ThreadSource>,
|
||||
/// When true, return only thread metadata and live fork state without
|
||||
/// populating `thread.turns`. This is useful when the client plans to call
|
||||
/// `thread/turns/list` immediately after forking.
|
||||
/// `thread/turns/list` immediately after forking. Full-history hydration
|
||||
/// is deprecated for paginated threads; use this with `thread/turns/list`
|
||||
/// and `thread/items/list` instead.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub exclude_turns: bool,
|
||||
/// When true, carry the source thread's current goal into the fork without
|
||||
@@ -1646,6 +1650,9 @@ pub enum ThreadActiveFlag {
|
||||
pub struct ThreadReadParams {
|
||||
pub thread_id: String,
|
||||
/// When true, include turns and their items from rollout history.
|
||||
/// Full-history hydration is deprecated for paginated threads; prefer a
|
||||
/// metadata-only read and page with `thread/turns/list` and
|
||||
/// `thread/items/list`.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub include_turns: bool,
|
||||
}
|
||||
|
||||
@@ -367,7 +367,7 @@ To continue a stored session, call `thread/resume` with the `thread.id` you prev
|
||||
|
||||
Parent-owned Multi-Agent V2 children are an exception: `thread/resume` ignores configuration overrides and reattaches to the existing child. An unloaded child is reloaded through its actual, currently loaded parent using parent-derived configuration. If that owner-controlled reload cannot be performed, the request returns JSON-RPC error `-32600`; resume the parent first, or use `thread/read` or `thread/turns/list` to inspect the child's stored history without loading it. This policy follows the child's multi-agent runtime, including leaf workers whose models cannot delegate further.
|
||||
|
||||
By default, `thread/resume` includes the reconstructed turn history in `thread.turns`. Clients can pass `excludeTurns: true` to return only thread metadata and live resume state, then call `thread/turns/list` separately if they want to page the turn history over the network. A cold paginated resume can still replay persisted `thread/tokenUsage/updated` when it can identify the corresponding stored turn; resuming an already-loaded thread waits for the next live update.
|
||||
By default, `thread/resume` includes the reconstructed turn history in `thread.turns`. Full-history hydration is deprecated for paginated threads and emits `deprecationNotice`; clients should pass `excludeTurns: true` to return only thread metadata and live resume state, then page with `thread/turns/list` and `thread/items/list`. A cold paginated resume can still replay persisted `thread/tokenUsage/updated` when it can identify the corresponding stored turn; resuming an already-loaded thread waits for the next live update.
|
||||
|
||||
Paginated threads keep the same resume contract as legacy threads. A default resume materializes the full projected history into `thread.turns`; `excludeTurns: true` keeps that array empty and includes `turnsBackwardsCursor` and `itemsBackwardsCursor` for the durable history visible at the resume boundary. Pass each cursor directly to its matching list API with `sortDirection: "desc"`; the first page includes the row identified by the cursor, while newer records arrive through live notifications. Either cursor is `null` when there is no durable row yet.
|
||||
|
||||
@@ -423,7 +423,7 @@ To branch from a stored session, call `thread/fork` with the `thread.id`. This c
|
||||
{ "method": "thread/started", "params": { "thread": { … } } }
|
||||
```
|
||||
|
||||
Like `thread/resume`, clients can pass `excludeTurns: true` to `thread/fork` to return only thread metadata in `thread.turns` and page history with `thread/turns/list`. Metadata-only forks do not replay restored `thread/tokenUsage/updated`. Ephemeral forks of paginated threads require `excludeTurns: true`.
|
||||
Like `thread/resume`, full-history hydration is deprecated for paginated `thread/fork` and emits `deprecationNotice`. Clients should pass `excludeTurns: true` to return only thread metadata in `thread.turns` and page history with `thread/turns/list` and `thread/items/list`. Metadata-only forks do not replay restored `thread/tokenUsage/updated`. Ephemeral forks of paginated threads require `excludeTurns: true`.
|
||||
|
||||
### Example: List threads (with pagination & filters)
|
||||
|
||||
@@ -574,8 +574,10 @@ Later, after the idle unload timeout:
|
||||
|
||||
Use `thread/read` to fetch a stored thread by id without resuming it. Pass `includeTurns` when you want thread history loaded into `thread.turns`. The returned thread includes `parentThreadId`, `agentNickname`, and `agentRole` for subagent threads when available.
|
||||
|
||||
Paginated threads can also use `includeTurns: true`, but clients should prefer
|
||||
`thread/turns/list` and `thread/items/list` for incremental history loading.
|
||||
Paginated threads can also use `includeTurns: true`, but full-history hydration
|
||||
is deprecated and emits `deprecationNotice`. Clients should omit `includeTurns`
|
||||
(or set it to `false`), then use `thread/turns/list` and `thread/items/list` for
|
||||
incremental history loading.
|
||||
|
||||
```json
|
||||
{ "method": "thread/read", "id": 22, "params": { "threadId": "thr_123" } }
|
||||
|
||||
@@ -1329,7 +1329,7 @@ impl MessageProcessor {
|
||||
self.thread_processor.thread_loaded_list(params).await
|
||||
}
|
||||
ClientRequest::ThreadRead { params, .. } => {
|
||||
self.thread_processor.thread_read(params).await
|
||||
self.thread_processor.thread_read(&request_id, params).await
|
||||
}
|
||||
ClientRequest::ThreadTurnsList { params, .. } => {
|
||||
self.thread_processor.thread_turns_list(params).await
|
||||
|
||||
@@ -7,6 +7,7 @@ use super::thread_input::ensure_direct_input_allowed;
|
||||
use super::*;
|
||||
use crate::error_code::method_not_found;
|
||||
use codex_app_server_protocol::SelectedCapabilityRoot;
|
||||
use codex_app_server_protocol::ThreadHistoryMode as ApiThreadHistoryMode;
|
||||
use codex_app_server_protocol::ThreadRevertParams;
|
||||
use codex_app_server_protocol::ThreadRevertResponse;
|
||||
use codex_app_server_protocol::ThreadRevertedNotification;
|
||||
@@ -27,6 +28,8 @@ pub(super) const THREAD_LIST_MAX_LIMIT: usize = 100;
|
||||
const CODEX_TUI_CLIENT_NAME: &str = "codex-tui";
|
||||
const THREAD_ROLLBACK_DEPRECATION_SUMMARY: &str =
|
||||
"thread/rollback is deprecated and will be removed soon";
|
||||
const PAGINATED_FULL_HISTORY_DEPRECATION_SUMMARY: &str = "Full-history hydration is deprecated for paginated threads; use `excludeTurns: true`, then page with `thread/turns/list` and `thread/items/list`.";
|
||||
const PAGINATED_THREAD_READ_DEPRECATION_SUMMARY: &str = "Full-history hydration is deprecated for paginated threads; omit `includeTurns` or set it to `false`, then page with `thread/turns/list` and `thread/items/list`.";
|
||||
|
||||
async fn stage_pending_project_metadata(
|
||||
thread_manager: &ThreadManager,
|
||||
@@ -787,20 +790,23 @@ impl ThreadRequestProcessor {
|
||||
app_server_client_name: Option<&str>,
|
||||
) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError> {
|
||||
if app_server_client_name != Some(CODEX_TUI_CLIENT_NAME) {
|
||||
self.send_thread_rollback_deprecation_notice(request_id.connection_id)
|
||||
.await;
|
||||
self.send_deprecation_notice(
|
||||
request_id.connection_id,
|
||||
THREAD_ROLLBACK_DEPRECATION_SUMMARY,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
self.thread_rollback_inner(request_id, params)
|
||||
.await
|
||||
.map(|()| None)
|
||||
}
|
||||
|
||||
async fn send_thread_rollback_deprecation_notice(&self, connection_id: ConnectionId) {
|
||||
async fn send_deprecation_notice(&self, connection_id: ConnectionId, summary: &str) {
|
||||
self.outgoing
|
||||
.send_server_notification_to_connections(
|
||||
&[connection_id],
|
||||
ServerNotification::DeprecationNotice(DeprecationNoticeNotification {
|
||||
summary: THREAD_ROLLBACK_DEPRECATION_SUMMARY.to_string(),
|
||||
summary: summary.to_string(),
|
||||
details: None,
|
||||
}),
|
||||
)
|
||||
@@ -845,11 +851,24 @@ impl ThreadRequestProcessor {
|
||||
|
||||
pub(crate) async fn thread_read(
|
||||
&self,
|
||||
request_id: &ConnectionRequestId,
|
||||
params: ThreadReadParams,
|
||||
) -> Result<Option<ClientResponsePayload>, JSONRPCErrorError> {
|
||||
self.thread_read_response_inner(params)
|
||||
.await
|
||||
.map(|response| Some(response.into()))
|
||||
let include_turns = params.include_turns;
|
||||
let response = self.thread_read_response_inner(params).await?;
|
||||
if include_turns
|
||||
&& matches!(
|
||||
response.thread.history_mode,
|
||||
ApiThreadHistoryMode::Paginated
|
||||
)
|
||||
{
|
||||
self.send_deprecation_notice(
|
||||
request_id.connection_id,
|
||||
PAGINATED_THREAD_READ_DEPRECATION_SUMMARY,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
Ok(Some(response.into()))
|
||||
}
|
||||
|
||||
pub(crate) async fn thread_turns_list(
|
||||
@@ -3636,6 +3655,13 @@ impl ThreadRequestProcessor {
|
||||
matches!(thread.history_mode, ThreadHistoryMode::Paginated).then_some(thread.thread_id)
|
||||
});
|
||||
let paginated_resume = paginated_thread_id.is_some();
|
||||
if paginated_resume && include_turns {
|
||||
self.send_deprecation_notice(
|
||||
request_id.connection_id,
|
||||
PAGINATED_FULL_HISTORY_DEPRECATION_SUMMARY,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
// Parent-owned V2 children must resume through their owner, not caller configuration.
|
||||
if let InitialHistory::Resumed(resumed_history) = &thread_history
|
||||
@@ -4144,6 +4170,13 @@ impl ThreadRequestProcessor {
|
||||
let redact_resume_payloads =
|
||||
should_redact_thread_resume_payloads(app_server_client_name.as_deref());
|
||||
let include_turns = !params.exclude_turns;
|
||||
if paginated_resume && include_turns {
|
||||
self.send_deprecation_notice(
|
||||
request_id.connection_id,
|
||||
PAGINATED_FULL_HISTORY_DEPRECATION_SUMMARY,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let needs_history =
|
||||
!paginated_resume && (include_turns || params.initial_turns_page.is_some());
|
||||
if needs_history {
|
||||
@@ -4660,6 +4693,13 @@ impl ThreadRequestProcessor {
|
||||
"ephemeral paginated thread/fork requires `excludeTurns: true`",
|
||||
));
|
||||
}
|
||||
if paginated_source && include_turns {
|
||||
self.send_deprecation_notice(
|
||||
request_id.connection_id,
|
||||
PAGINATED_FULL_HISTORY_DEPRECATION_SUMMARY,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let source_thread_id = source_thread.thread_id;
|
||||
let source_thread_name = source_thread
|
||||
.name
|
||||
|
||||
@@ -14,6 +14,7 @@ use codex_app_server_protocol::ActivePermissionProfile;
|
||||
use codex_app_server_protocol::ApprovalsReviewer;
|
||||
use codex_app_server_protocol::AskForApproval;
|
||||
use codex_app_server_protocol::ClientRequest;
|
||||
use codex_app_server_protocol::DeprecationNoticeNotification;
|
||||
use codex_app_server_protocol::JSONRPCError;
|
||||
use codex_app_server_protocol::JSONRPCMessage;
|
||||
use codex_app_server_protocol::JSONRPCResponse;
|
||||
@@ -1494,6 +1495,76 @@ async fn thread_fork_creates_reference_backed_paginated_thread() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_fork_warns_for_paginated_full_history_hydration() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
let codex_home = TempDir::new()?;
|
||||
MockResponsesConfig::new(&server.uri()).write(codex_home.path())?;
|
||||
|
||||
let conversation_id = create_fake_paginated_rollout(
|
||||
codex_home.path(),
|
||||
"2025-01-05T12-00-00",
|
||||
"2025-01-05T12:00:00Z",
|
||||
"Saved user message",
|
||||
Some("mock_provider"),
|
||||
/*git_info*/ None,
|
||||
)?;
|
||||
let mut mcp = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
.without_auto_env()
|
||||
.build_initialized()
|
||||
.await?;
|
||||
|
||||
let fork_id = mcp
|
||||
.send_thread_fork_request(ThreadForkParams {
|
||||
thread_id: conversation_id.clone(),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let _: DeprecationNoticeNotification = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_notification("deprecationNotice"),
|
||||
)
|
||||
.await??;
|
||||
let _: ThreadForkResponse = timeout(DEFAULT_READ_TIMEOUT, mcp.read_response(fork_id)).await??;
|
||||
|
||||
mcp.clear_message_buffer();
|
||||
let metadata_fork_id = mcp
|
||||
.send_thread_fork_request(ThreadForkParams {
|
||||
thread_id: conversation_id.clone(),
|
||||
exclude_turns: true,
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let _: ThreadForkResponse =
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.read_response(metadata_fork_id)).await??;
|
||||
assert!(
|
||||
!mcp.pending_notification_methods()
|
||||
.contains(&"deprecationNotice".to_string())
|
||||
);
|
||||
|
||||
mcp.clear_message_buffer();
|
||||
let invalid_fork_id = mcp
|
||||
.send_thread_fork_request(ThreadForkParams {
|
||||
thread_id: conversation_id,
|
||||
last_turn_id: Some("turn-1".to_string()),
|
||||
before_turn_id: Some("turn-1".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let _: JSONRPCError = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_stream_until_error_message(RequestId::Integer(invalid_fork_id)),
|
||||
)
|
||||
.await??;
|
||||
assert!(
|
||||
!mcp.pending_notification_methods()
|
||||
.contains(&"deprecationNotice".to_string())
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_fork_freezes_active_paginated_turn_as_interrupted() -> Result<()> {
|
||||
assert_thread_fork_freezes_active_paginated_turn_as_interrupted(MultiAgentVersion::V1).await
|
||||
|
||||
@@ -11,6 +11,7 @@ use codex_app_server::in_process;
|
||||
use codex_app_server::in_process::InProcessStartArgs;
|
||||
use codex_app_server_protocol::ClientInfo;
|
||||
use codex_app_server_protocol::ClientRequest;
|
||||
use codex_app_server_protocol::DeprecationNoticeNotification;
|
||||
use codex_app_server_protocol::InitializeCapabilities;
|
||||
use codex_app_server_protocol::InitializeParams;
|
||||
use codex_app_server_protocol::JSONRPCError;
|
||||
@@ -209,6 +210,10 @@ async fn thread_read_can_include_turns() -> Result<()> {
|
||||
other => panic!("expected user message item, got {other:?}"),
|
||||
}
|
||||
assert_eq!(thread.status, ThreadStatus::NotLoaded);
|
||||
assert!(
|
||||
!mcp.pending_notification_methods()
|
||||
.contains(&"deprecationNotice".to_string())
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -244,6 +249,31 @@ async fn paginated_stored_thread_routes_projected_turns() -> Result<()> {
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.read_response(read_id)).await??;
|
||||
assert_eq!(thread.history_mode, ThreadHistoryMode::Paginated);
|
||||
assert!(thread.turns.is_empty());
|
||||
assert!(
|
||||
!mcp.pending_notification_methods()
|
||||
.contains(&"deprecationNotice".to_string())
|
||||
);
|
||||
|
||||
let full_read_id = mcp
|
||||
.send_thread_read_request(ThreadReadParams {
|
||||
thread_id: conversation_id.clone(),
|
||||
include_turns: true,
|
||||
})
|
||||
.await?;
|
||||
let notice: DeprecationNoticeNotification = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_notification("deprecationNotice"),
|
||||
)
|
||||
.await??;
|
||||
assert_eq!(
|
||||
notice,
|
||||
DeprecationNoticeNotification {
|
||||
summary: "Full-history hydration is deprecated for paginated threads; omit `includeTurns` or set it to `false`, then page with `thread/turns/list` and `thread/items/list`.".to_string(),
|
||||
details: None,
|
||||
}
|
||||
);
|
||||
let _: ThreadReadResponse =
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.read_response(full_read_id)).await??;
|
||||
|
||||
let list_id = mcp
|
||||
.send_thread_list_request(ThreadListParams {
|
||||
|
||||
@@ -24,6 +24,7 @@ use codex_app_server_protocol::ClientInfo;
|
||||
use codex_app_server_protocol::ClientRequest;
|
||||
use codex_app_server_protocol::CommandExecutionApprovalDecision;
|
||||
use codex_app_server_protocol::CommandExecutionRequestApprovalResponse;
|
||||
use codex_app_server_protocol::DeprecationNoticeNotification;
|
||||
use codex_app_server_protocol::FileChangeApprovalDecision;
|
||||
use codex_app_server_protocol::FileChangeRequestApprovalResponse;
|
||||
use codex_app_server_protocol::ItemStartedNotification;
|
||||
@@ -2385,6 +2386,80 @@ async fn thread_resume_can_skip_turns_for_metadata_only_resume() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_resume_warns_for_paginated_full_history_hydration() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
let codex_home = TempDir::new()?;
|
||||
mock_responses_config(&server.uri()).write(codex_home.path())?;
|
||||
|
||||
let conversation_id = create_fake_paginated_rollout(
|
||||
codex_home.path(),
|
||||
"2025-01-05T12-00-00",
|
||||
"2025-01-05T12:00:00Z",
|
||||
"Saved user message",
|
||||
Some("mock_provider"),
|
||||
/*git_info*/ None,
|
||||
)?;
|
||||
let mut mcp = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
.without_auto_env()
|
||||
.build_initialized()
|
||||
.await?;
|
||||
|
||||
let cold_resume_id = mcp
|
||||
.send_thread_resume_request(ThreadResumeParams {
|
||||
thread_id: conversation_id.clone(),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let notice: DeprecationNoticeNotification = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_notification("deprecationNotice"),
|
||||
)
|
||||
.await??;
|
||||
assert_eq!(
|
||||
notice,
|
||||
DeprecationNoticeNotification {
|
||||
summary: "Full-history hydration is deprecated for paginated threads; use `excludeTurns: true`, then page with `thread/turns/list` and `thread/items/list`.".to_string(),
|
||||
details: None,
|
||||
}
|
||||
);
|
||||
let _: ThreadResumeResponse =
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.read_response(cold_resume_id)).await??;
|
||||
|
||||
mcp.clear_message_buffer();
|
||||
let loaded_resume_id = mcp
|
||||
.send_thread_resume_request(ThreadResumeParams {
|
||||
thread_id: conversation_id.clone(),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let _: DeprecationNoticeNotification = timeout(
|
||||
DEFAULT_READ_TIMEOUT,
|
||||
mcp.read_notification("deprecationNotice"),
|
||||
)
|
||||
.await??;
|
||||
let _: ThreadResumeResponse =
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.read_response(loaded_resume_id)).await??;
|
||||
|
||||
mcp.clear_message_buffer();
|
||||
let metadata_resume_id = mcp
|
||||
.send_thread_resume_request(ThreadResumeParams {
|
||||
thread_id: conversation_id,
|
||||
exclude_turns: true,
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let _: ThreadResumeResponse =
|
||||
timeout(DEFAULT_READ_TIMEOUT, mcp.read_response(metadata_resume_id)).await??;
|
||||
assert!(
|
||||
!mcp.pending_notification_methods()
|
||||
.contains(&"deprecationNotice".to_string())
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_resume_rejects_archived_session_by_id() -> Result<()> {
|
||||
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
||||
|
||||
+1
-1
@@ -4,4 +4,4 @@ expression: rendered_retry
|
||||
---
|
||||
› Handle the safety-buffered request
|
||||
Keep the accepted steer
|
||||
• done
|
||||
⚠ Full-history hydration is deprecated for paginated threads; omit `includeTurns` or set it to `false`, then page with `thread/turns/list` and `thread/items/list`.• done
|
||||
|
||||
Reference in New Issue
Block a user