mirror of
https://github.com/openai/codex.git
synced 2026-11-14 21:40:39 +00:00
## Why Clients need to refresh section membership and ordering when another client moves a thread. ## What changed Broadcast `thread/section/changed` after a successful `thread/section/move`, including moves within the same section. Send the response before the notification, and include `threadId` and the destination `sectionId`, using `null` for removal. Failed moves do not emit the notification. Add the notification to the protocol schemas, TypeScript exports, and Python SDK models and registry, and document its behavior. ## Testing Add a WebSocket integration test with separate writer and observer clients covering section insertion, reordering, removal, and a failed move. GitOrigin-RevId: 128ba7eddc9aae8e3bac766f47e4a2c55c64a6a5
226 lines
11 KiB
Rust
226 lines
11 KiB
Rust
use codex_app_server_protocol::DynamicToolCallOutputContentItem;
|
|
use codex_app_server_protocol::GeneratedImageRef;
|
|
use codex_app_server_protocol::ServerNotification;
|
|
use codex_app_server_protocol::ThreadItemPayload;
|
|
use codex_app_server_protocol::UserInput;
|
|
use codex_protocol::models::ContentItem;
|
|
use codex_protocol::models::FunctionCallOutputBody;
|
|
use codex_protocol::models::FunctionCallOutputContentItem;
|
|
use codex_protocol::models::ResponseItem;
|
|
|
|
pub(crate) fn without_notification_media(notification: ServerNotification) -> ServerNotification {
|
|
match notification {
|
|
ServerNotification::ItemStarted(mut notification) => {
|
|
notification.item.payload = without_thread_item_media(notification.item.payload);
|
|
ServerNotification::ItemStarted(notification)
|
|
}
|
|
ServerNotification::ItemCompleted(mut notification) => {
|
|
notification.item.payload = without_thread_item_media(notification.item.payload);
|
|
ServerNotification::ItemCompleted(notification)
|
|
}
|
|
ServerNotification::RawResponseItemCompleted(mut notification) => {
|
|
match &mut notification.item {
|
|
ResponseItem::Message { content, .. } => {
|
|
content.retain(|item| match item {
|
|
ContentItem::InputImage { .. } | ContentItem::InputAudio { .. } => false,
|
|
ContentItem::InputText { .. } | ContentItem::OutputText { .. } => true,
|
|
});
|
|
}
|
|
ResponseItem::FunctionCallOutput { output, .. }
|
|
| ResponseItem::CustomToolCallOutput { output, .. } => {
|
|
if let Some(items) = output.content_items_mut() {
|
|
items.retain(|item| match item {
|
|
FunctionCallOutputContentItem::InputImage { .. }
|
|
| FunctionCallOutputContentItem::InputAudio { .. } => false,
|
|
FunctionCallOutputContentItem::InputText { .. }
|
|
| FunctionCallOutputContentItem::EncryptedContent { .. } => true,
|
|
});
|
|
}
|
|
}
|
|
ResponseItem::ImageGenerationCall { result, .. } => result.clear(),
|
|
ResponseItem::AdditionalTools { .. }
|
|
| ResponseItem::AgentMessage { .. }
|
|
| ResponseItem::Reasoning { .. }
|
|
| ResponseItem::LocalShellCall { .. }
|
|
| ResponseItem::FunctionCall { .. }
|
|
| ResponseItem::ToolSearchCall { .. }
|
|
| ResponseItem::CustomToolCall { .. }
|
|
| ResponseItem::ToolSearchOutput { .. }
|
|
| ResponseItem::WebSearchCall { .. }
|
|
| ResponseItem::Compaction { .. }
|
|
| ResponseItem::ConfigurationUpdate { .. }
|
|
| ResponseItem::CompactionTrigger { .. }
|
|
| ResponseItem::ContextCompaction { .. }
|
|
| ResponseItem::Other => {}
|
|
}
|
|
ServerNotification::RawResponseItemCompleted(notification)
|
|
}
|
|
ServerNotification::Error(_)
|
|
| ServerNotification::ThreadStarted(_)
|
|
| ServerNotification::ThreadStatusChanged(_)
|
|
| ServerNotification::ThreadReadStateChanged(_)
|
|
| ServerNotification::ThreadArchived(_)
|
|
| ServerNotification::ThreadDeleted(_)
|
|
| ServerNotification::ThreadUnarchived(_)
|
|
| ServerNotification::ThreadClosed(_)
|
|
| ServerNotification::ThreadReverted(_)
|
|
| ServerNotification::SkillsChanged(_)
|
|
| ServerNotification::ThreadNameUpdated(_)
|
|
| ServerNotification::ThreadSectionChanged(_)
|
|
| ServerNotification::ThreadAttachmentUpdated(_)
|
|
| ServerNotification::ThreadGoalUpdated(_)
|
|
| ServerNotification::ThreadPredictionUpdated(_)
|
|
| ServerNotification::ThreadGoalCleared(_)
|
|
| ServerNotification::ThreadQueueChanged(_)
|
|
| ServerNotification::ProjectChanged(_)
|
|
| ServerNotification::ThreadProjectUpdated(_)
|
|
| ServerNotification::EnvironmentConnected(_)
|
|
| ServerNotification::EnvironmentDisconnected(_)
|
|
| ServerNotification::ThreadSettingsUpdated(_)
|
|
| ServerNotification::ThreadTokenUsageUpdated(_)
|
|
| ServerNotification::TurnStarted(_)
|
|
| ServerNotification::HookStarted(_)
|
|
| ServerNotification::TurnCompleted(_)
|
|
| ServerNotification::HookCompleted(_)
|
|
| ServerNotification::TurnDiffUpdated(_)
|
|
| ServerNotification::TurnPlanUpdated(_)
|
|
| ServerNotification::ItemGuardianApprovalReviewStarted(_)
|
|
| ServerNotification::ItemGuardianApprovalReviewCompleted(_)
|
|
| ServerNotification::StrictReviewRequired(_)
|
|
| ServerNotification::RawResponseCompleted(_)
|
|
| ServerNotification::AgentMessageDelta(_)
|
|
| ServerNotification::PlanDelta(_)
|
|
| ServerNotification::CommandExecOutputDelta(_)
|
|
| ServerNotification::ProcessOutputDelta(_)
|
|
| ServerNotification::ProcessExited(_)
|
|
| ServerNotification::CommandExecutionOutputDelta(_)
|
|
| ServerNotification::TerminalInteraction(_)
|
|
| ServerNotification::FileChangeOutputDelta(_)
|
|
| ServerNotification::FileChangePatchUpdated(_)
|
|
| ServerNotification::ServerRequestResolved(_)
|
|
| ServerNotification::McpToolCallProgress(_)
|
|
| ServerNotification::McpServerOauthLoginCompleted(_)
|
|
| ServerNotification::McpServerStatusUpdated(_)
|
|
| ServerNotification::McpServerEventStream(_)
|
|
| ServerNotification::AccountUpdated(_)
|
|
| ServerNotification::AccountRateLimitsUpdated(_)
|
|
| ServerNotification::AppListUpdated(_)
|
|
| ServerNotification::RemoteControlStatusChanged(_)
|
|
| ServerNotification::ExternalAgentConfigImportProgress(_)
|
|
| ServerNotification::ExternalAgentConfigImportCompleted(_)
|
|
| ServerNotification::FsChanged(_)
|
|
| ServerNotification::ReasoningSummaryTextDelta(_)
|
|
| ServerNotification::ReasoningSummaryPartAdded(_)
|
|
| ServerNotification::ReasoningTextDelta(_)
|
|
| ServerNotification::ContextCompacted(_)
|
|
| ServerNotification::ModelRerouted(_)
|
|
| ServerNotification::ModelVerification(_)
|
|
| ServerNotification::AuthRecoveryStarted(_)
|
|
| ServerNotification::AuthRecoveryCompleted(_)
|
|
| ServerNotification::TurnModerationMetadata(_)
|
|
| ServerNotification::ModelSafetyBufferingUpdated(_)
|
|
| ServerNotification::Warning(_)
|
|
| ServerNotification::GuardianWarning(_)
|
|
| ServerNotification::DeprecationNotice(_)
|
|
| ServerNotification::ConfigWarning(_)
|
|
| ServerNotification::FuzzyFileSearchSessionUpdated(_)
|
|
| ServerNotification::FuzzyFileSearchSessionCompleted(_)
|
|
| ServerNotification::ThreadRealtimeStarted(_)
|
|
| ServerNotification::ThreadRealtimeItemAdded(_)
|
|
| ServerNotification::ThreadRealtimeItemStarted(_)
|
|
| ServerNotification::ThreadRealtimeItemTranscriptDelta(_)
|
|
| ServerNotification::ThreadRealtimeItemCompleted(_)
|
|
| ServerNotification::ThreadRealtimeTranscriptDelta(_)
|
|
| ServerNotification::ThreadRealtimeTranscriptDone(_)
|
|
| ServerNotification::ThreadRealtimeOutputAudioDelta(_)
|
|
| ServerNotification::ThreadRealtimeSdp(_)
|
|
| ServerNotification::ThreadRealtimeError(_)
|
|
| ServerNotification::ThreadRealtimeClosed(_)
|
|
| ServerNotification::WindowsWorldWritableWarning(_)
|
|
| ServerNotification::WindowsSandboxSetupCompleted(_)
|
|
| ServerNotification::AccountLoginCompleted(_)
|
|
| ServerNotification::GatewayOAuthChanged(_) => notification,
|
|
}
|
|
}
|
|
|
|
fn without_thread_item_media(mut item: ThreadItemPayload) -> ThreadItemPayload {
|
|
match &mut item {
|
|
ThreadItemPayload::UserMessage { content, .. } => {
|
|
content.retain(|item| match item {
|
|
UserInput::Image { .. } | UserInput::Audio { .. } => false,
|
|
UserInput::Text { .. }
|
|
| UserInput::LocalImage { .. }
|
|
| UserInput::LocalAudio { .. }
|
|
| UserInput::Skill { .. }
|
|
| UserInput::Mention { .. } => true,
|
|
});
|
|
}
|
|
ThreadItemPayload::DynamicToolCall {
|
|
content_items: Some(content_items),
|
|
..
|
|
} => {
|
|
content_items.retain(|item| match item {
|
|
DynamicToolCallOutputContentItem::InputImage { .. }
|
|
| DynamicToolCallOutputContentItem::InputAudio { .. } => false,
|
|
DynamicToolCallOutputContentItem::InputText { .. } => true,
|
|
});
|
|
}
|
|
ThreadItemPayload::FunctionCallOutput {
|
|
output: FunctionCallOutputBody::ContentItems(items),
|
|
..
|
|
} => {
|
|
items.retain(|item| match item {
|
|
FunctionCallOutputContentItem::InputImage { .. }
|
|
| FunctionCallOutputContentItem::InputAudio { .. } => false,
|
|
FunctionCallOutputContentItem::InputText { .. }
|
|
| FunctionCallOutputContentItem::EncryptedContent { .. } => true,
|
|
});
|
|
}
|
|
ThreadItemPayload::McpToolCall {
|
|
result: Some(result),
|
|
..
|
|
} => {
|
|
// TODO(ruslan): Handle oversized results that core has already collapsed into a
|
|
// truncated text preview, which can contain inline media that this filter retains.
|
|
result.content.retain(|item| {
|
|
!matches!(
|
|
item.get("type").and_then(serde_json::Value::as_str),
|
|
Some("image" | "audio")
|
|
) && item
|
|
.get("resource")
|
|
.and_then(|resource| resource.get("blob"))
|
|
.is_none()
|
|
});
|
|
}
|
|
ThreadItemPayload::ImageGeneration(item) => {
|
|
item.image = GeneratedImageRef::Inline {
|
|
result: String::new(),
|
|
};
|
|
}
|
|
ThreadItemPayload::HookPrompt { .. }
|
|
| ThreadItemPayload::AgentMessage { .. }
|
|
| ThreadItemPayload::Plan { .. }
|
|
| ThreadItemPayload::Reasoning { .. }
|
|
| ThreadItemPayload::FunctionCallOutput {
|
|
output: FunctionCallOutputBody::Text(_),
|
|
..
|
|
}
|
|
| ThreadItemPayload::CommandExecution { .. }
|
|
| ThreadItemPayload::FileChange { .. }
|
|
| ThreadItemPayload::McpToolCall { result: None, .. }
|
|
| ThreadItemPayload::DynamicToolCall {
|
|
content_items: None,
|
|
..
|
|
}
|
|
| ThreadItemPayload::CollabAgentToolCall { .. }
|
|
| ThreadItemPayload::SubAgentActivity { .. }
|
|
| ThreadItemPayload::WebSearch(_)
|
|
| ThreadItemPayload::ImageView { .. }
|
|
| ThreadItemPayload::Sleep(_)
|
|
| ThreadItemPayload::EnteredReviewMode { .. }
|
|
| ThreadItemPayload::ExitedReviewMode { .. }
|
|
| ThreadItemPayload::ContextCompaction { .. } => {}
|
|
}
|
|
item
|
|
}
|