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:
Owen Lin
2026-08-25 19:06:42 +00:00
committed by copyberry
parent 5cb7a35de9
commit d132b69219
19 changed files with 261 additions and 29 deletions
+3 -3
View File
@@ -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": {
@@ -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": {
@@ -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": {
@@ -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,
}
+6 -4
View File
@@ -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" } }
+1 -1
View File
@@ -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;
@@ -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