mirror of
https://github.com/openai/codex.git
synced 2026-10-21 17:48:44 +00:00
## What changed - Upload generated images to the attachment store and expose `fileId` in image-generation items when available, with inline `result` fallback on upload or decoding failure. - Preserve file references through legacy events, persisted rollouts, thread history, and task output summaries. Keep model-facing image preparation separate. - Add attachment request tags and mark generated-image uploads with `usage=image_gen`, preserving the thread's ephemeral setting. - Update public schemas and SDK types to accept either image representation, retain the Python `ImageGenerationThreadItem` alias, and prefer `fileId` when reading records containing both fields. ## Testing Add coverage for file-backed generation and rollout persistence, legacy history replay, inline and transitional serialization, task output visibility, and Python SDK alias compatibility. GitOrigin-RevId: f7a2bb5707e2f277348a547a988bc33c8e96d891
225 lines
10 KiB
Rust
225 lines
10 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::ThreadItem;
|
|
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 = without_thread_item_media(notification.item);
|
|
ServerNotification::ItemStarted(notification)
|
|
}
|
|
ServerNotification::ItemCompleted(mut notification) => {
|
|
notification.item = without_thread_item_media(notification.item);
|
|
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::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: ThreadItem) -> ThreadItem {
|
|
match &mut item {
|
|
ThreadItem::UserMessage { content, .. } => {
|
|
content.retain(|item| match item {
|
|
UserInput::Image { .. } | UserInput::Audio { .. } => false,
|
|
UserInput::Text { .. }
|
|
| UserInput::LocalImage { .. }
|
|
| UserInput::LocalAudio { .. }
|
|
| UserInput::Skill { .. }
|
|
| UserInput::Mention { .. } => true,
|
|
});
|
|
}
|
|
ThreadItem::DynamicToolCall {
|
|
content_items: Some(content_items),
|
|
..
|
|
} => {
|
|
content_items.retain(|item| match item {
|
|
DynamicToolCallOutputContentItem::InputImage { .. }
|
|
| DynamicToolCallOutputContentItem::InputAudio { .. } => false,
|
|
DynamicToolCallOutputContentItem::InputText { .. } => true,
|
|
});
|
|
}
|
|
ThreadItem::FunctionCallOutput {
|
|
output: FunctionCallOutputBody::ContentItems(items),
|
|
..
|
|
} => {
|
|
items.retain(|item| match item {
|
|
FunctionCallOutputContentItem::InputImage { .. }
|
|
| FunctionCallOutputContentItem::InputAudio { .. } => false,
|
|
FunctionCallOutputContentItem::InputText { .. }
|
|
| FunctionCallOutputContentItem::EncryptedContent { .. } => true,
|
|
});
|
|
}
|
|
ThreadItem::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()
|
|
});
|
|
}
|
|
ThreadItem::ImageGeneration(item) => {
|
|
item.image = GeneratedImageRef::Inline {
|
|
result: String::new(),
|
|
};
|
|
}
|
|
ThreadItem::HookPrompt { .. }
|
|
| ThreadItem::AgentMessage { .. }
|
|
| ThreadItem::Plan { .. }
|
|
| ThreadItem::Reasoning { .. }
|
|
| ThreadItem::FunctionCallOutput {
|
|
output: FunctionCallOutputBody::Text(_),
|
|
..
|
|
}
|
|
| ThreadItem::CommandExecution { .. }
|
|
| ThreadItem::FileChange { .. }
|
|
| ThreadItem::McpToolCall { result: None, .. }
|
|
| ThreadItem::DynamicToolCall {
|
|
content_items: None,
|
|
..
|
|
}
|
|
| ThreadItem::CollabAgentToolCall { .. }
|
|
| ThreadItem::SubAgentActivity { .. }
|
|
| ThreadItem::WebSearch(_)
|
|
| ThreadItem::ImageView { .. }
|
|
| ThreadItem::Sleep(_)
|
|
| ThreadItem::EnteredReviewMode { .. }
|
|
| ThreadItem::ExitedReviewMode { .. }
|
|
| ThreadItem::ContextCompaction { .. } => {}
|
|
}
|
|
item
|
|
}
|