mirror of
https://github.com/openai/codex.git
synced 2026-11-03 19:53:25 +00:00
add uuid to metadata
This commit is contained in:
@@ -1998,6 +1998,13 @@
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"uuid": {
|
||||
"description": "Client-visible UUID generated by Codex for this item.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -10589,6 +10589,13 @@
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"uuid": {
|
||||
"description": "Client-visible UUID generated by Codex for this item.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -7377,6 +7377,13 @@
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"uuid": {
|
||||
"description": "Client-visible UUID generated by Codex for this item.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -836,6 +836,13 @@
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"uuid": {
|
||||
"description": "Client-visible UUID generated by Codex for this item.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -902,6 +902,13 @@
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"uuid": {
|
||||
"description": "Client-visible UUID generated by Codex for this item.",
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
||||
@@ -3,4 +3,8 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { UserMessageType } from "./UserMessageType";
|
||||
|
||||
export type ResponseItemMetadata = { user_message_type?: UserMessageType, };
|
||||
export type ResponseItemMetadata = { user_message_type?: UserMessageType,
|
||||
/**
|
||||
* Client-visible UUID generated by Codex for this item.
|
||||
*/
|
||||
uuid?: string, };
|
||||
|
||||
@@ -2723,8 +2723,11 @@ mod tests {
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::TurnPlanStepStatus;
|
||||
use codex_protocol::mcp::CallToolResult;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::FileSystemPermissions as CoreFileSystemPermissions;
|
||||
use codex_protocol::models::NetworkPermissions as CoreNetworkPermissions;
|
||||
use codex_protocol::models::ResponseItem;
|
||||
use codex_protocol::models::ResponseItemMetadata;
|
||||
use codex_protocol::plan_tool::PlanItemArg;
|
||||
use codex_protocol::plan_tool::StepStatus;
|
||||
use codex_protocol::protocol::CollabResumeBeginEvent;
|
||||
@@ -3756,4 +3759,74 @@ mod tests {
|
||||
assert!(rx.try_recv().is_err(), "no messages expected");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn raw_response_item_completed_preserves_existing_metadata_uuid() -> Result<()> {
|
||||
let (tx, mut rx) = mpsc::channel(CHANNEL_CAPACITY);
|
||||
let outgoing = Arc::new(OutgoingMessageSender::new(tx));
|
||||
let conversation_id = ThreadId::new();
|
||||
let outgoing = ThreadScopedOutgoingMessageSender::new(
|
||||
outgoing,
|
||||
vec![ConnectionId(1)],
|
||||
conversation_id.clone(),
|
||||
);
|
||||
let expected_uuid = uuid::Uuid::new_v4().to_string();
|
||||
|
||||
maybe_emit_raw_response_item_completed(
|
||||
ApiVersion::V2,
|
||||
conversation_id.clone(),
|
||||
"turn-1",
|
||||
ResponseItem::Message {
|
||||
id: Some("msg_123".to_string()),
|
||||
role: "assistant".to_string(),
|
||||
content: vec![ContentItem::OutputText {
|
||||
text: "hello".to_string(),
|
||||
}],
|
||||
metadata: Some(ResponseItemMetadata {
|
||||
user_message_type: None,
|
||||
uuid: Some(expected_uuid.clone()),
|
||||
}),
|
||||
end_turn: None,
|
||||
phase: None,
|
||||
},
|
||||
&outgoing,
|
||||
)
|
||||
.await;
|
||||
|
||||
let msg = recv_broadcast_message(&mut rx).await?;
|
||||
match msg {
|
||||
OutgoingMessage::AppServerNotification(
|
||||
ServerNotification::RawResponseItemCompleted(notification),
|
||||
) => {
|
||||
assert_eq!(notification.thread_id, conversation_id.to_string());
|
||||
assert_eq!(notification.turn_id, "turn-1");
|
||||
match notification.item {
|
||||
ResponseItem::Message {
|
||||
id,
|
||||
metadata,
|
||||
role,
|
||||
content,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(id, Some("msg_123".to_string()));
|
||||
assert_eq!(role, "assistant");
|
||||
assert_eq!(
|
||||
content,
|
||||
vec![ContentItem::OutputText {
|
||||
text: "hello".to_string()
|
||||
}]
|
||||
);
|
||||
let metadata = metadata.expect("metadata should be present");
|
||||
assert_eq!(metadata.user_message_type, None);
|
||||
let uuid = metadata.uuid.expect("uuid should be present");
|
||||
assert_eq!(uuid, expected_uuid);
|
||||
}
|
||||
other => bail!("unexpected response item: {other:?}"),
|
||||
}
|
||||
}
|
||||
other => bail!("unexpected message: {other:?}"),
|
||||
}
|
||||
assert!(rx.try_recv().is_err(), "no extra messages expected");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,12 @@ pub struct Prompt {
|
||||
|
||||
impl Prompt {
|
||||
pub(crate) fn get_formatted_input(&self) -> Vec<ResponseItem> {
|
||||
let mut input = self.input.clone();
|
||||
let mut input: Vec<ResponseItem> = self
|
||||
.input
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(ResponseItem::with_generated_metadata_uuid)
|
||||
.collect();
|
||||
|
||||
// when using the *Freeform* apply_patch tool specifically, tool outputs
|
||||
// should be structured text, not json. Do NOT reserialize when using
|
||||
|
||||
@@ -3,7 +3,10 @@ use codex_api::common::OpenAiVerbosity;
|
||||
use codex_api::common::TextControls;
|
||||
use codex_api::create_text_param_for_request;
|
||||
use codex_protocol::config_types::ServiceTier;
|
||||
use codex_protocol::models::ContentItem;
|
||||
use codex_protocol::models::FunctionCallOutputPayload;
|
||||
use codex_protocol::models::ResponseItemMetadata;
|
||||
use codex_protocol::models::UserMessageType;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
@@ -196,6 +199,39 @@ fn reserializes_shell_outputs_for_function_and_custom_tool_calls() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn formatted_input_generates_message_metadata_uuid() {
|
||||
let prompt = Prompt {
|
||||
input: vec![ResponseItem::Message {
|
||||
id: Some("msg_123".to_string()),
|
||||
role: "user".to_string(),
|
||||
content: vec![ContentItem::InputText {
|
||||
text: "hello".to_string(),
|
||||
}],
|
||||
metadata: Some(ResponseItemMetadata {
|
||||
user_message_type: Some(UserMessageType::Prompt),
|
||||
uuid: None,
|
||||
}),
|
||||
end_turn: None,
|
||||
phase: None,
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let formatted = prompt.get_formatted_input();
|
||||
assert_eq!(formatted.len(), 1);
|
||||
|
||||
match &formatted[0] {
|
||||
ResponseItem::Message { metadata, .. } => {
|
||||
let metadata = metadata.as_ref().expect("metadata should be present");
|
||||
assert_eq!(metadata.user_message_type, Some(UserMessageType::Prompt));
|
||||
let uuid = metadata.uuid.as_deref().expect("uuid should be present");
|
||||
uuid::Uuid::parse_str(uuid).expect("uuid should be valid");
|
||||
}
|
||||
other => panic!("expected message item, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tool_search_output_namespace_serializes_with_deferred_child_tools() {
|
||||
let namespace = tools::ToolSearchOutputTool::Namespace(tools::ResponsesApiNamespace {
|
||||
|
||||
@@ -3277,9 +3277,14 @@ impl Session {
|
||||
turn_context: &TurnContext,
|
||||
items: &[ResponseItem],
|
||||
) {
|
||||
self.record_into_history(items, turn_context).await;
|
||||
self.persist_rollout_response_items(items).await;
|
||||
self.send_raw_response_items(turn_context, items).await;
|
||||
let items: Vec<ResponseItem> = items
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(ResponseItem::with_generated_metadata_uuid)
|
||||
.collect();
|
||||
self.record_into_history(&items, turn_context).await;
|
||||
self.persist_rollout_response_items(&items).await;
|
||||
self.send_raw_response_items(turn_context, &items).await;
|
||||
}
|
||||
|
||||
/// Append ResponseItems to the in-memory conversation history only.
|
||||
|
||||
@@ -267,6 +267,47 @@ pub struct ResponseItemMetadata {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub user_message_type: Option<UserMessageType>,
|
||||
/// Client-visible UUID generated by Codex for this item.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub uuid: Option<String>,
|
||||
}
|
||||
|
||||
impl ResponseItem {
|
||||
/// Ensures message metadata includes a generated UUID.
|
||||
pub fn with_generated_metadata_uuid(self) -> Self {
|
||||
match self {
|
||||
ResponseItem::Message {
|
||||
id,
|
||||
role,
|
||||
content,
|
||||
metadata,
|
||||
end_turn,
|
||||
phase,
|
||||
} => {
|
||||
let metadata = ensure_generated_metadata_uuid(metadata);
|
||||
ResponseItem::Message {
|
||||
id,
|
||||
role,
|
||||
content,
|
||||
metadata,
|
||||
end_turn,
|
||||
phase,
|
||||
}
|
||||
}
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_generated_metadata_uuid(
|
||||
metadata: Option<ResponseItemMetadata>,
|
||||
) -> Option<ResponseItemMetadata> {
|
||||
let mut metadata = metadata.unwrap_or_default();
|
||||
if metadata.uuid.is_none() {
|
||||
metadata.uuid = Some(uuid::Uuid::new_v4().to_string());
|
||||
}
|
||||
Some(metadata)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema, TS)]
|
||||
@@ -2958,4 +2999,51 @@ mod tests {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generates_metadata_uuid_for_message_items() -> Result<()> {
|
||||
let item = ResponseItem::Message {
|
||||
id: Some("msg_123".to_string()),
|
||||
role: "assistant".to_string(),
|
||||
content: vec![ContentItem::OutputText {
|
||||
text: "hello".to_string(),
|
||||
}],
|
||||
metadata: Some(ResponseItemMetadata {
|
||||
user_message_type: Some(UserMessageType::Prompt),
|
||||
uuid: None,
|
||||
}),
|
||||
end_turn: None,
|
||||
phase: None,
|
||||
};
|
||||
|
||||
let surfaced = item.with_generated_metadata_uuid();
|
||||
let serialized = serde_json::to_value(&surfaced)?;
|
||||
let metadata = serialized
|
||||
.get("metadata")
|
||||
.and_then(serde_json::Value::as_object)
|
||||
.expect("metadata should be present");
|
||||
let uuid = metadata
|
||||
.get("uuid")
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.expect("uuid should be present");
|
||||
|
||||
uuid::Uuid::parse_str(uuid).expect("uuid should be valid");
|
||||
|
||||
assert_eq!(serialized.get("type"), Some(&serde_json::json!("message")));
|
||||
assert_eq!(
|
||||
serialized.get("role"),
|
||||
Some(&serde_json::json!("assistant"))
|
||||
);
|
||||
assert_eq!(
|
||||
serialized.get("content"),
|
||||
Some(&serde_json::json!([{ "type": "output_text", "text": "hello" }]))
|
||||
);
|
||||
assert_eq!(
|
||||
metadata.get("user_message_type"),
|
||||
Some(&serde_json::json!("prompt"))
|
||||
);
|
||||
assert_ne!(uuid, "msg_123");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user