mirror of
https://github.com/openai/codex.git
synced 2026-11-09 00:39:32 +00:00
Support plaintext collaboration tool messages (#35845)
## What changed - Preserve `encrypted_function_args` on function calls so an empty list can mark plaintext collaboration arguments across request replay. - Deliver `spawn_agent`, `send_message`, and `followup_task` payloads as structured plaintext agent messages when that marker is present; retain encrypted delivery otherwise. - Redact plaintext collaboration arguments from tool and communication logs, and omit the metadata when sending requests to non-OpenAI providers. ## Testing - Cover serialization of empty encrypted-argument metadata and plaintext versus encrypted subagent message delivery. - Verify plaintext tool arguments are redacted and provider-specific metadata is removed from non-OpenAI requests. GitOrigin-RevId: 64db98ff0b61a3af2f04ed609292363f2e2362a8
This commit is contained in:
1 parent
166658a34a
commit
03edf16f0b
36 files changed
+390
-76
No files matched your search
@@ -2854,6 +2854,15 @@
|
|||||||
"call_id": {
|
"call_id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"encrypted_function_args": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
|
|||||||
+9
@@ -16337,6 +16337,15 @@
|
|||||||
"call_id": {
|
"call_id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"encrypted_function_args": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
|
|||||||
+9
@@ -12679,6 +12679,15 @@
|
|||||||
"call_id": {
|
"call_id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"encrypted_function_args": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
|
|||||||
+9
@@ -632,6 +632,15 @@
|
|||||||
"call_id": {
|
"call_id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"encrypted_function_args": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
|
|||||||
@@ -702,6 +702,15 @@
|
|||||||
"call_id": {
|
"call_id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"encrypted_function_args": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": [
|
||||||
|
"array",
|
||||||
|
"null"
|
||||||
|
]
|
||||||
|
},
|
||||||
"id": {
|
"id": {
|
||||||
"type": [
|
"type": [
|
||||||
"string",
|
"string",
|
||||||
|
|||||||
@@ -21,4 +21,4 @@ id?: ResponseItemId,
|
|||||||
/**
|
/**
|
||||||
* Set when using the Responses API.
|
* Set when using the Responses API.
|
||||||
*/
|
*/
|
||||||
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "function_call", id?: ResponseItemId, name: string, namespace?: string, arguments: string, call_id: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "tool_search_call", id?: ResponseItemId, call_id: string | null, status?: string, execution: string, arguments: unknown, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "function_call_output", id?: ResponseItemId, call_id: string, output: FunctionCallOutputBody, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "custom_tool_call", id?: ResponseItemId, status?: string, call_id: string, name: string, namespace?: string, input: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "custom_tool_call_output", id?: ResponseItemId, call_id: string, name?: string, output: FunctionCallOutputBody, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "tool_search_output", id?: ResponseItemId, call_id: string | null, status: string, execution: string, tools: unknown[], internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "web_search_call", id?: ResponseItemId, status?: string, action?: WebSearchAction, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "image_generation_call", id?: ResponseItemId, status: string, revised_prompt?: string, result: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "compaction", id?: ResponseItemId, encrypted_content: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "compaction_trigger", } | { "type": "context_compaction", id?: ResponseItemId, encrypted_content?: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "other" };
|
call_id: string | null, status: LocalShellStatus, action: LocalShellAction, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "function_call", id?: ResponseItemId, name: string, namespace?: string, arguments: string, encrypted_function_args?: Array<string>, call_id: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "tool_search_call", id?: ResponseItemId, call_id: string | null, status?: string, execution: string, arguments: unknown, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "function_call_output", id?: ResponseItemId, call_id: string, output: FunctionCallOutputBody, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "custom_tool_call", id?: ResponseItemId, status?: string, call_id: string, name: string, namespace?: string, input: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "custom_tool_call_output", id?: ResponseItemId, call_id: string, name?: string, output: FunctionCallOutputBody, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "tool_search_output", id?: ResponseItemId, call_id: string | null, status: string, execution: string, tools: unknown[], internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "web_search_call", id?: ResponseItemId, status?: string, action?: WebSearchAction, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "image_generation_call", id?: ResponseItemId, status: string, revised_prompt?: string, result: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "compaction", id?: ResponseItemId, encrypted_content: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "compaction_trigger", } | { "type": "context_compaction", id?: ResponseItemId, encrypted_content?: string, internal_chat_message_metadata_passthrough?: InternalChatMessageMetadataPassthrough, } | { "type": "other" };
|
||||||
@@ -120,6 +120,7 @@ fn spawn_agent_call(call_id: &str) -> ResponseItem {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: call_id.to_string(),
|
call_id: call_id.to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,11 +56,10 @@ pub(crate) fn emit_agent_communication_send(
|
|||||||
state = "send",
|
state = "send",
|
||||||
sender_thread_id = %context.sender_thread_id,
|
sender_thread_id = %context.sender_thread_id,
|
||||||
receiver_thread_id = %receiver_thread_id,
|
receiver_thread_id = %receiver_thread_id,
|
||||||
content = if communication.content.is_empty() {
|
content = communication
|
||||||
communication.encrypted_content.as_deref().unwrap_or_default()
|
.encrypted_content
|
||||||
} else {
|
.as_deref()
|
||||||
communication.content.as_str()
|
.unwrap_or("[plaintext]"),
|
||||||
},
|
|
||||||
},
|
},
|
||||||
"agent communication"
|
"agent communication"
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -848,9 +848,16 @@ impl ModelClient {
|
|||||||
let mut input = prompt.get_formatted_input_for_request(model_info.use_responses_lite);
|
let mut input = prompt.get_formatted_input_for_request(model_info.use_responses_lite);
|
||||||
let is_openai = self.state.provider.info().is_openai();
|
let is_openai = self.state.provider.info().is_openai();
|
||||||
if !is_openai {
|
if !is_openai {
|
||||||
input
|
for item in &mut input {
|
||||||
.iter_mut()
|
item.clear_internal_chat_message_metadata_passthrough();
|
||||||
.for_each(ResponseItem::clear_internal_chat_message_metadata_passthrough);
|
if let ResponseItem::FunctionCall {
|
||||||
|
encrypted_function_args,
|
||||||
|
..
|
||||||
|
} = item
|
||||||
|
{
|
||||||
|
*encrypted_function_args = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let (instructions, tools) = if model_info.use_responses_lite {
|
let (instructions, tools) = if model_info.use_responses_lite {
|
||||||
let tools = create_tools_json_for_responses_api(&prompt.tools)?;
|
let tools = create_tools_json_for_responses_api(&prompt.tools)?;
|
||||||
|
|||||||
@@ -620,6 +620,7 @@ mod tests {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "call_1".to_string(),
|
call_id: "call_1".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
ResponseItem::Compaction {
|
ResponseItem::Compaction {
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
use codex_protocol::AgentPath;
|
||||||
|
|
||||||
|
use super::ContextualUserFragment;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub(crate) enum InterAgentMessageType {
|
||||||
|
Message,
|
||||||
|
NewTask,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterAgentMessageType {
|
||||||
|
fn as_str(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Message => "MESSAGE",
|
||||||
|
Self::NewTask => "NEW_TASK",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub(crate) struct InterAgentMessage {
|
||||||
|
message_type: InterAgentMessageType,
|
||||||
|
task_name: AgentPath,
|
||||||
|
sender: AgentPath,
|
||||||
|
payload: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InterAgentMessage {
|
||||||
|
pub(crate) fn new(
|
||||||
|
message_type: InterAgentMessageType,
|
||||||
|
task_name: AgentPath,
|
||||||
|
sender: AgentPath,
|
||||||
|
payload: impl Into<String>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
message_type,
|
||||||
|
task_name,
|
||||||
|
sender,
|
||||||
|
payload: payload.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ContextualUserFragment for InterAgentMessage {
|
||||||
|
fn role(&self) -> &'static str {
|
||||||
|
"assistant"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn markers(&self) -> (&'static str, &'static str) {
|
||||||
|
Self::type_markers()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_markers() -> (&'static str, &'static str) {
|
||||||
|
("", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn body(&self) -> String {
|
||||||
|
format!(
|
||||||
|
"Message Type: {}\nTask name: {}\nSender: {}\nPayload:\n{}",
|
||||||
|
self.message_type.as_str(),
|
||||||
|
self.task_name,
|
||||||
|
self.sender,
|
||||||
|
self.payload,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ mod environments_instructions;
|
|||||||
mod guardian_followup_review_reminder;
|
mod guardian_followup_review_reminder;
|
||||||
mod hook_additional_context;
|
mod hook_additional_context;
|
||||||
mod inter_agent_completion_message;
|
mod inter_agent_completion_message;
|
||||||
|
mod inter_agent_message;
|
||||||
mod internal_model_context;
|
mod internal_model_context;
|
||||||
mod legacy_apply_patch_exec_command_warning;
|
mod legacy_apply_patch_exec_command_warning;
|
||||||
mod legacy_model_mismatch_warning;
|
mod legacy_model_mismatch_warning;
|
||||||
@@ -51,6 +52,8 @@ pub(crate) use environments_instructions::EnvironmentsInstructions;
|
|||||||
pub(crate) use guardian_followup_review_reminder::GuardianFollowupReviewReminder;
|
pub(crate) use guardian_followup_review_reminder::GuardianFollowupReviewReminder;
|
||||||
pub(crate) use hook_additional_context::HookAdditionalContext;
|
pub(crate) use hook_additional_context::HookAdditionalContext;
|
||||||
pub(crate) use inter_agent_completion_message::InterAgentCompletionMessage;
|
pub(crate) use inter_agent_completion_message::InterAgentCompletionMessage;
|
||||||
|
pub(crate) use inter_agent_message::InterAgentMessage;
|
||||||
|
pub(crate) use inter_agent_message::InterAgentMessageType;
|
||||||
pub use internal_model_context::InternalContextSource;
|
pub use internal_model_context::InternalContextSource;
|
||||||
pub use internal_model_context::InternalModelContextFragment;
|
pub use internal_model_context::InternalModelContextFragment;
|
||||||
pub use internal_model_context::InvalidInternalContextSource;
|
pub use internal_model_context::InvalidInternalContextSource;
|
||||||
|
|||||||
@@ -550,6 +550,7 @@ fn for_prompt_strips_media_when_model_does_not_support_it() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "call-1".to_string(),
|
call_id: "call-1".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
ResponseItem::FunctionCallOutput {
|
ResponseItem::FunctionCallOutput {
|
||||||
@@ -631,6 +632,7 @@ fn for_prompt_strips_media_when_model_does_not_support_it() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "call-1".to_string(),
|
call_id: "call-1".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
ResponseItem::FunctionCallOutput {
|
ResponseItem::FunctionCallOutput {
|
||||||
@@ -851,6 +853,7 @@ fn remove_first_item_removes_matching_output_for_function_call() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "call-1".to_string(),
|
call_id: "call-1".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
ResponseItem::FunctionCallOutput {
|
ResponseItem::FunctionCallOutput {
|
||||||
@@ -880,6 +883,7 @@ fn remove_first_item_removes_matching_call_for_output() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "call-2".to_string(),
|
call_id: "call-2".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -1392,6 +1396,7 @@ fn normalize_adds_missing_output_for_function_call() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "call-x".to_string(),
|
call_id: "call-x".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
}];
|
}];
|
||||||
let mut h = create_history_with_items(items);
|
let mut h = create_history_with_items(items);
|
||||||
@@ -1407,6 +1412,7 @@ fn normalize_adds_missing_output_for_function_call() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "call-x".to_string(),
|
call_id: "call-x".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
ResponseItem::FunctionCallOutput {
|
ResponseItem::FunctionCallOutput {
|
||||||
@@ -1548,6 +1554,7 @@ fn normalize_mixed_inserts_and_removals() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "c1".to_string(),
|
call_id: "c1".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
// Orphan output that should be removed
|
// Orphan output that should be removed
|
||||||
@@ -1595,6 +1602,7 @@ fn normalize_mixed_inserts_and_removals() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "c1".to_string(),
|
call_id: "c1".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
ResponseItem::FunctionCallOutput {
|
ResponseItem::FunctionCallOutput {
|
||||||
@@ -1650,6 +1658,7 @@ fn normalize_adds_missing_output_for_function_call_inserts_output() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "call-x".to_string(),
|
call_id: "call-x".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
}];
|
}];
|
||||||
let mut h = create_history_with_items(items);
|
let mut h = create_history_with_items(items);
|
||||||
@@ -1663,6 +1672,7 @@ fn normalize_adds_missing_output_for_function_call_inserts_output() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "call-x".to_string(),
|
call_id: "call-x".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
ResponseItem::FunctionCallOutput {
|
ResponseItem::FunctionCallOutput {
|
||||||
@@ -1684,6 +1694,7 @@ fn for_prompt_assigns_stable_id_to_synthetic_output_without_reordering_history()
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "call-x".to_string(),
|
call_id: "call-x".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
ResponseItem::Message {
|
ResponseItem::Message {
|
||||||
@@ -1894,6 +1905,7 @@ fn normalize_mixed_inserts_and_removals_panics_in_debug() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "c1".to_string(),
|
call_id: "c1".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
ResponseItem::FunctionCallOutput {
|
ResponseItem::FunctionCallOutput {
|
||||||
|
|||||||
@@ -308,6 +308,7 @@ async fn seed_guardian_parent_history(session: &Arc<Session>, turn: &Arc<TurnCon
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{\"repo\":\"openai/codex\"}".to_string(),
|
arguments: "{\"repo\":\"openai/codex\"}".to_string(),
|
||||||
call_id: "call-1".to_string(),
|
call_id: "call-1".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
ResponseItem::FunctionCallOutput {
|
ResponseItem::FunctionCallOutput {
|
||||||
@@ -830,6 +831,7 @@ fn collect_guardian_transcript_entries_includes_recent_tool_calls_and_output() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{\"path\":\"README.md\"}".to_string(),
|
arguments: "{\"path\":\"README.md\"}".to_string(),
|
||||||
call_id: "call-1".to_string(),
|
call_id: "call-1".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
ResponseItem::FunctionCallOutput {
|
ResponseItem::FunctionCallOutput {
|
||||||
|
|||||||
@@ -10983,6 +10983,7 @@ async fn tool_calls_reopen_mailbox_delivery_for_current_turn() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "call-1".to_string(),
|
call_id: "call-1".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
};
|
};
|
||||||
let mut ctx = HandleOutputCtx {
|
let mut ctx = HandleOutputCtx {
|
||||||
@@ -11446,6 +11447,7 @@ while :; do sleep 1; done"#,
|
|||||||
})
|
})
|
||||||
.to_string(),
|
.to_string(),
|
||||||
call_id: "shell-cleanup-call".to_string(),
|
call_id: "shell-cleanup-call".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
};
|
};
|
||||||
let call = ToolRouter::build_tool_call(item)?
|
let call = ToolRouter::build_tool_call(item)?
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use crate::session::session::Session;
|
|||||||
use crate::session::turn_context::TurnContext;
|
use crate::session::turn_context::TurnContext;
|
||||||
use crate::tools::parallel::ToolCallRuntime;
|
use crate::tools::parallel::ToolCallRuntime;
|
||||||
use crate::tools::router::ToolRouter;
|
use crate::tools::router::ToolRouter;
|
||||||
|
use crate::tools::router::tool_log_payload;
|
||||||
use codex_memories_read::citations::parse_memory_citation;
|
use codex_memories_read::citations::parse_memory_citation;
|
||||||
use codex_memories_read::citations::thread_ids_from_memory_citation;
|
use codex_memories_read::citations::thread_ids_from_memory_citation;
|
||||||
use codex_protocol::error::CodexErr;
|
use codex_protocol::error::CodexErr;
|
||||||
@@ -303,7 +304,7 @@ pub(crate) async fn handle_output_item_done(
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let payload_preview = call.payload.log_payload().into_owned();
|
let payload_preview = tool_log_payload(&call.payload, &call.direct_source());
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
thread_id = %ctx.sess.thread_id,
|
thread_id = %ctx.sess.thread_id,
|
||||||
"ToolCall: {} {}",
|
"ToolCall: {} {}",
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ fn external_context_pollution_items_exclude_local_tool_calls() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "call-1".to_string(),
|
call_id: "call-1".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
ResponseItem::FunctionCallOutput {
|
ResponseItem::FunctionCallOutput {
|
||||||
|
|||||||
@@ -203,6 +203,7 @@ fn truncates_before_requested_user_message() {
|
|||||||
name: "tool".to_string(),
|
name: "tool".to_string(),
|
||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
assistant_msg("a4"),
|
assistant_msg("a4"),
|
||||||
|
|||||||
@@ -233,6 +233,7 @@ fn truncates_rollout_from_start_before_nth_user_only() {
|
|||||||
name: "tool".to_string(),
|
name: "tool".to_string(),
|
||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
assistant_msg("a4"),
|
assistant_msg("a4"),
|
||||||
|
|||||||
@@ -318,6 +318,7 @@ async fn call_nested_tool(
|
|||||||
tool_name,
|
tool_name,
|
||||||
call_id: format!("{PUBLIC_TOOL_NAME}-{}", uuid::Uuid::new_v4()),
|
call_id: format!("{PUBLIC_TOOL_NAME}-{}", uuid::Uuid::new_v4()),
|
||||||
payload,
|
payload,
|
||||||
|
encrypted_function_args: None,
|
||||||
};
|
};
|
||||||
let result = tool_runtime
|
let result = tool_runtime
|
||||||
.handle_tool_call_with_source(
|
.handle_tool_call_with_source(
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ pub type SharedTurnDiffTracker = Arc<Mutex<TurnDiffTracker>>;
|
|||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum ToolCallSource {
|
pub enum ToolCallSource {
|
||||||
Direct,
|
Direct,
|
||||||
|
DirectPlaintextMessage,
|
||||||
CodeMode {
|
CodeMode {
|
||||||
/// Runtime cell that issued the nested tool request.
|
/// Runtime cell that issued the nested tool request.
|
||||||
cell_id: String,
|
cell_id: String,
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
use crate::agent::AgentStatus;
|
use crate::agent::AgentStatus;
|
||||||
use crate::agent::agent_resolver::resolve_agent_target;
|
use crate::agent::agent_resolver::resolve_agent_target;
|
||||||
|
use crate::context::ContextualUserFragment;
|
||||||
|
use crate::context::InterAgentMessage;
|
||||||
|
use crate::context::InterAgentMessageType;
|
||||||
use crate::function_tool::FunctionCallError;
|
use crate::function_tool::FunctionCallError;
|
||||||
use crate::tools::context::ToolInvocation;
|
use crate::tools::context::ToolInvocation;
|
||||||
use crate::tools::context::ToolOutput;
|
use crate::tools::context::ToolOutput;
|
||||||
@@ -51,16 +54,31 @@ pub(crate) async fn emit_sub_agent_activity(
|
|||||||
session.emit_turn_item_completed(turn, item).await;
|
session.emit_turn_item_completed(turn, item).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn communication_from_tool_message(
|
fn communication_from_tool_message(
|
||||||
author: AgentPath,
|
author: AgentPath,
|
||||||
recipient: AgentPath,
|
recipient: AgentPath,
|
||||||
message: String,
|
message: String,
|
||||||
|
source: &crate::tools::context::ToolCallSource,
|
||||||
|
trigger_turn: bool,
|
||||||
) -> InterAgentCommunication {
|
) -> InterAgentCommunication {
|
||||||
InterAgentCommunication::new_encrypted(
|
if !matches!(
|
||||||
author,
|
source,
|
||||||
recipient,
|
crate::tools::context::ToolCallSource::DirectPlaintextMessage
|
||||||
Vec::new(),
|
) {
|
||||||
message,
|
return InterAgentCommunication::new_encrypted(
|
||||||
/*trigger_turn*/ true,
|
author,
|
||||||
)
|
recipient,
|
||||||
|
Vec::new(),
|
||||||
|
message,
|
||||||
|
trigger_turn,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let message_type = if trigger_turn {
|
||||||
|
InterAgentMessageType::NewTask
|
||||||
|
} else {
|
||||||
|
InterAgentMessageType::Message
|
||||||
|
};
|
||||||
|
let content =
|
||||||
|
InterAgentMessage::new(message_type, recipient.clone(), author.clone(), message).render();
|
||||||
|
InterAgentCommunication::new(author, recipient, Vec::new(), content, trigger_turn)
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,6 @@ use super::*;
|
|||||||
use crate::agent_communication::AgentCommunicationContext;
|
use crate::agent_communication::AgentCommunicationContext;
|
||||||
use crate::agent_communication::AgentCommunicationKind;
|
use crate::agent_communication::AgentCommunicationKind;
|
||||||
use crate::tools::context::FunctionToolOutput;
|
use crate::tools::context::FunctionToolOutput;
|
||||||
use codex_protocol::protocol::InterAgentCommunication;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub(crate) enum MessageDeliveryMode {
|
pub(crate) enum MessageDeliveryMode {
|
||||||
@@ -16,17 +15,10 @@ pub(crate) enum MessageDeliveryMode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl MessageDeliveryMode {
|
impl MessageDeliveryMode {
|
||||||
/// Returns whether the produced communication should start a turn immediately.
|
fn trigger_turn(self) -> bool {
|
||||||
fn apply(self, communication: InterAgentCommunication) -> InterAgentCommunication {
|
|
||||||
match self {
|
match self {
|
||||||
Self::QueueOnly => InterAgentCommunication {
|
Self::QueueOnly => false,
|
||||||
trigger_turn: false,
|
Self::TriggerTurn => true,
|
||||||
..communication
|
|
||||||
},
|
|
||||||
Self::TriggerTurn => InterAgentCommunication {
|
|
||||||
trigger_turn: true,
|
|
||||||
..communication
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,6 +60,7 @@ pub(crate) async fn handle_message_string_tool(
|
|||||||
session,
|
session,
|
||||||
turn,
|
turn,
|
||||||
call_id,
|
call_id,
|
||||||
|
source,
|
||||||
..
|
..
|
||||||
} = invocation;
|
} = invocation;
|
||||||
let receiver_thread_id = resolve_agent_target(&session, &turn, &target).await?;
|
let receiver_thread_id = resolve_agent_target(&session, &turn, &target).await?;
|
||||||
@@ -100,8 +93,13 @@ pub(crate) async fn handle_message_string_tool(
|
|||||||
.session_source
|
.session_source
|
||||||
.get_agent_path()
|
.get_agent_path()
|
||||||
.unwrap_or_else(AgentPath::root);
|
.unwrap_or_else(AgentPath::root);
|
||||||
let communication =
|
let communication = communication_from_tool_message(
|
||||||
communication_from_tool_message(author, receiver_agent_path.clone(), message);
|
author,
|
||||||
|
receiver_agent_path.clone(),
|
||||||
|
message,
|
||||||
|
&source,
|
||||||
|
mode.trigger_turn(),
|
||||||
|
);
|
||||||
let kind = match mode {
|
let kind = match mode {
|
||||||
MessageDeliveryMode::QueueOnly => AgentCommunicationKind::Message,
|
MessageDeliveryMode::QueueOnly => AgentCommunicationKind::Message,
|
||||||
MessageDeliveryMode::TriggerTurn => AgentCommunicationKind::Followup,
|
MessageDeliveryMode::TriggerTurn => AgentCommunicationKind::Followup,
|
||||||
@@ -112,12 +110,7 @@ pub(crate) async fn handle_message_string_tool(
|
|||||||
let result = session
|
let result = session
|
||||||
.services
|
.services
|
||||||
.agent_control
|
.agent_control
|
||||||
.send_inter_agent_communication(
|
.send_inter_agent_communication(receiver_thread_id, communication, context, parent_turn_id)
|
||||||
receiver_thread_id,
|
|
||||||
mode.apply(communication),
|
|
||||||
context,
|
|
||||||
parent_turn_id,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.map_err(|err| collab_agent_error(receiver_thread_id, err));
|
.map_err(|err| collab_agent_error(receiver_thread_id, err));
|
||||||
result?;
|
result?;
|
||||||
|
|||||||
@@ -44,18 +44,19 @@ async fn handle_spawn_agent(
|
|||||||
turn,
|
turn,
|
||||||
payload,
|
payload,
|
||||||
call_id,
|
call_id,
|
||||||
|
source,
|
||||||
..
|
..
|
||||||
} = invocation;
|
} = invocation;
|
||||||
let arguments = function_arguments(payload)?;
|
let arguments = function_arguments(payload)?;
|
||||||
let args: SpawnAgentArgs = parse_arguments(&arguments)?;
|
let args: SpawnAgentArgs = parse_arguments(&arguments)?;
|
||||||
let fork_mode = args.fork_mode()?;
|
let fork_mode = args.fork_mode()?;
|
||||||
|
let message = message_content(args.message)?;
|
||||||
let role_name = args
|
let role_name = args
|
||||||
.agent_type
|
.agent_type
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.map(str::trim)
|
.map(str::trim)
|
||||||
.filter(|role| !role.is_empty());
|
.filter(|role| !role.is_empty());
|
||||||
|
|
||||||
let message = message_content(args.message)?;
|
|
||||||
let session_source = turn.session_source.clone();
|
let session_source = turn.session_source.clone();
|
||||||
let child_depth = next_thread_spawn_depth(&session_source);
|
let child_depth = next_thread_spawn_depth(&session_source);
|
||||||
let mut config =
|
let mut config =
|
||||||
@@ -103,7 +104,13 @@ async fn handle_spawn_agent(
|
|||||||
.session_source
|
.session_source
|
||||||
.get_agent_path()
|
.get_agent_path()
|
||||||
.unwrap_or_else(AgentPath::root);
|
.unwrap_or_else(AgentPath::root);
|
||||||
let communication = communication_from_tool_message(author, new_agent_path.clone(), message);
|
let communication = communication_from_tool_message(
|
||||||
|
author,
|
||||||
|
new_agent_path.clone(),
|
||||||
|
message,
|
||||||
|
&source,
|
||||||
|
/*trigger_turn*/ true,
|
||||||
|
);
|
||||||
let context = AgentCommunicationContext::new(AgentCommunicationKind::Spawn, session.thread_id);
|
let context = AgentCommunicationContext::new(AgentCommunicationKind::Spawn, session.thread_id);
|
||||||
let spawned_agent = Box::pin(
|
let spawned_agent = Box::pin(
|
||||||
session
|
session
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ async fn notify_tool_finish_parts(
|
|||||||
|
|
||||||
fn extension_tool_call_source(source: ToolCallSource) -> ExtensionToolCallSource {
|
fn extension_tool_call_source(source: ToolCallSource) -> ExtensionToolCallSource {
|
||||||
match source {
|
match source {
|
||||||
ToolCallSource::Direct => ExtensionToolCallSource::Direct,
|
ToolCallSource::Direct | ToolCallSource::DirectPlaintextMessage => {
|
||||||
|
ExtensionToolCallSource::Direct
|
||||||
|
}
|
||||||
ToolCallSource::CodeMode {
|
ToolCallSource::CodeMode {
|
||||||
cell_id,
|
cell_id,
|
||||||
runtime_tool_call_id,
|
runtime_tool_call_id,
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ impl ToolCallRuntime {
|
|||||||
cancellation_token: CancellationToken,
|
cancellation_token: CancellationToken,
|
||||||
) -> impl std::future::Future<Output = Result<ResponseInputItem, CodexErr>> {
|
) -> impl std::future::Future<Output = Result<ResponseInputItem, CodexErr>> {
|
||||||
let error_call = call.clone();
|
let error_call = call.clone();
|
||||||
let future =
|
let source = call.direct_source();
|
||||||
self.handle_tool_call_with_source(call, ToolCallSource::Direct, cancellation_token);
|
let future = self.handle_tool_call_with_source(call, source, cancellation_token);
|
||||||
async move {
|
async move {
|
||||||
match future.await {
|
match future.await {
|
||||||
Ok(response) => Ok(response.into_response()),
|
Ok(response) => Ok(response.into_response()),
|
||||||
@@ -270,7 +270,11 @@ impl ToolCallTimingGuard {
|
|||||||
// Code-mode calls are nested within a direct code-mode tool call whose
|
// Code-mode calls are nested within a direct code-mode tool call whose
|
||||||
// timing already includes them. Suppress nested guards so consumers do
|
// timing already includes them. Suppress nested guards so consumers do
|
||||||
// not mistake overlapping events for independent tool-call latency.
|
// not mistake overlapping events for independent tool-call latency.
|
||||||
if !matches!(source, ToolCallSource::Direct) || !tracing::enabled!(tracing::Level::INFO) {
|
if !matches!(
|
||||||
|
source,
|
||||||
|
ToolCallSource::Direct | ToolCallSource::DirectPlaintextMessage
|
||||||
|
) || !tracing::enabled!(tracing::Level::INFO)
|
||||||
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -373,6 +377,7 @@ mod tests {
|
|||||||
payload: ToolPayload::Function {
|
payload: ToolPayload::Function {
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
},
|
},
|
||||||
|
encrypted_function_args: None,
|
||||||
};
|
};
|
||||||
let direct_guard = ToolCallTimingGuard::capture(
|
let direct_guard = ToolCallTimingGuard::capture(
|
||||||
Instant::now(),
|
Instant::now(),
|
||||||
@@ -449,6 +454,7 @@ mod tests {
|
|||||||
payload: ToolPayload::Function {
|
payload: ToolPayload::Function {
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
},
|
},
|
||||||
|
encrypted_function_args: None,
|
||||||
};
|
};
|
||||||
let response_task =
|
let response_task =
|
||||||
tokio::spawn(runtime.handle_tool_call(call, cancellation_token.clone()));
|
tokio::spawn(runtime.handle_tool_call(call, cancellation_token.clone()));
|
||||||
@@ -692,6 +698,7 @@ mod tests {
|
|||||||
payload: ToolPayload::Function {
|
payload: ToolPayload::Function {
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
},
|
},
|
||||||
|
encrypted_function_args: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_task =
|
let response_task =
|
||||||
@@ -765,6 +772,7 @@ mod tests {
|
|||||||
payload: ToolPayload::Function {
|
payload: ToolPayload::Function {
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
},
|
},
|
||||||
|
encrypted_function_args: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let response_task =
|
let response_task =
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ use crate::tools::handlers::multi_agents_spec::MULTI_AGENT_V1_NAMESPACE;
|
|||||||
use crate::tools::hook_names::HookToolName;
|
use crate::tools::hook_names::HookToolName;
|
||||||
use crate::tools::lifecycle::notify_tool_finish;
|
use crate::tools::lifecycle::notify_tool_finish;
|
||||||
use crate::tools::lifecycle::notify_tool_start;
|
use crate::tools::lifecycle::notify_tool_start;
|
||||||
|
use crate::tools::router::tool_log_payload;
|
||||||
use crate::tools::tool_dispatch_trace::ToolDispatchTrace;
|
use crate::tools::tool_dispatch_trace::ToolDispatchTrace;
|
||||||
use crate::util::error_or_panic;
|
use crate::util::error_or_panic;
|
||||||
use codex_extension_api::ToolCallOutcome;
|
use codex_extension_api::ToolCallOutcome;
|
||||||
@@ -465,7 +466,7 @@ impl ToolRegistry {
|
|||||||
Some(tool) => tool,
|
Some(tool) => tool,
|
||||||
None => {
|
None => {
|
||||||
let message = unsupported_tool_call_message(&invocation.payload, &tool_name);
|
let message = unsupported_tool_call_message(&invocation.payload, &tool_name);
|
||||||
let log_payload = invocation.payload.log_payload();
|
let log_payload = tool_log_payload(&invocation.payload, &invocation.source);
|
||||||
otel.tool_result_with_tags(
|
otel.tool_result_with_tags(
|
||||||
tool_name_flat.as_ref(),
|
tool_name_flat.as_ref(),
|
||||||
&call_id_owned,
|
&call_id_owned,
|
||||||
@@ -496,7 +497,7 @@ impl ToolRegistry {
|
|||||||
}
|
}
|
||||||
if !tool.matches_kind(&invocation.payload) {
|
if !tool.matches_kind(&invocation.payload) {
|
||||||
let message = format!("tool {tool_name} invoked with incompatible payload");
|
let message = format!("tool {tool_name} invoked with incompatible payload");
|
||||||
let log_payload = invocation.payload.log_payload();
|
let log_payload = tool_log_payload(&invocation.payload, &invocation.source);
|
||||||
otel.tool_result_with_tags(
|
otel.tool_result_with_tags(
|
||||||
tool_name_flat.as_ref(),
|
tool_name_flat.as_ref(),
|
||||||
&call_id_owned,
|
&call_id_owned,
|
||||||
@@ -578,7 +579,7 @@ impl ToolRegistry {
|
|||||||
|
|
||||||
let response_cell = tokio::sync::Mutex::new(None);
|
let response_cell = tokio::sync::Mutex::new(None);
|
||||||
let invocation_for_tool = invocation.clone();
|
let invocation_for_tool = invocation.clone();
|
||||||
let log_payload = invocation.payload.log_payload();
|
let log_payload = tool_log_payload(&invocation.payload, &invocation.source);
|
||||||
|
|
||||||
let result = otel
|
let result = otel
|
||||||
.log_tool_result_with_tags(
|
.log_tool_result_with_tags(
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ use codex_tools::ToolCall as ExtensionToolCall;
|
|||||||
use codex_tools::ToolExecutor;
|
use codex_tools::ToolExecutor;
|
||||||
use codex_tools::ToolName;
|
use codex_tools::ToolName;
|
||||||
use codex_tools::ToolSpec;
|
use codex_tools::ToolSpec;
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::atomic::AtomicBool;
|
use std::sync::atomic::AtomicBool;
|
||||||
@@ -33,6 +34,36 @@ pub struct ToolCall {
|
|||||||
pub tool_name: ToolName,
|
pub tool_name: ToolName,
|
||||||
pub call_id: String,
|
pub call_id: String,
|
||||||
pub payload: ToolPayload,
|
pub payload: ToolPayload,
|
||||||
|
pub encrypted_function_args: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToolCall {
|
||||||
|
pub(crate) fn direct_source(&self) -> ToolCallSource {
|
||||||
|
if self.tool_name.namespace.as_deref() == Some("collaboration")
|
||||||
|
&& matches!(
|
||||||
|
self.tool_name.name.as_str(),
|
||||||
|
"spawn_agent" | "send_message" | "followup_task"
|
||||||
|
)
|
||||||
|
&& self
|
||||||
|
.encrypted_function_args
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(Vec::is_empty)
|
||||||
|
{
|
||||||
|
ToolCallSource::DirectPlaintextMessage
|
||||||
|
} else {
|
||||||
|
ToolCallSource::Direct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn tool_log_payload<'a>(
|
||||||
|
payload: &'a ToolPayload,
|
||||||
|
source: &ToolCallSource,
|
||||||
|
) -> Cow<'a, str> {
|
||||||
|
if matches!(source, ToolCallSource::DirectPlaintextMessage) {
|
||||||
|
return Cow::Borrowed("[plaintext arguments]");
|
||||||
|
}
|
||||||
|
payload.log_payload()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ToolRouter {
|
pub struct ToolRouter {
|
||||||
@@ -131,6 +162,7 @@ impl ToolRouter {
|
|||||||
name,
|
name,
|
||||||
namespace,
|
namespace,
|
||||||
arguments,
|
arguments,
|
||||||
|
encrypted_function_args,
|
||||||
call_id,
|
call_id,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
@@ -139,6 +171,7 @@ impl ToolRouter {
|
|||||||
tool_name,
|
tool_name,
|
||||||
call_id,
|
call_id,
|
||||||
payload: ToolPayload::Function { arguments },
|
payload: ToolPayload::Function { arguments },
|
||||||
|
encrypted_function_args,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
ResponseItem::ToolSearchCall {
|
ResponseItem::ToolSearchCall {
|
||||||
@@ -157,6 +190,7 @@ impl ToolRouter {
|
|||||||
tool_name: ToolName::plain("tool_search"),
|
tool_name: ToolName::plain("tool_search"),
|
||||||
call_id,
|
call_id,
|
||||||
payload: ToolPayload::ToolSearch { arguments },
|
payload: ToolPayload::ToolSearch { arguments },
|
||||||
|
encrypted_function_args: None,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
ResponseItem::ToolSearchCall { .. } => Ok(None),
|
ResponseItem::ToolSearchCall { .. } => Ok(None),
|
||||||
@@ -170,6 +204,7 @@ impl ToolRouter {
|
|||||||
tool_name: ToolName::new(namespace, name),
|
tool_name: ToolName::new(namespace, name),
|
||||||
call_id,
|
call_id,
|
||||||
payload: ToolPayload::Custom { input },
|
payload: ToolPayload::Custom { input },
|
||||||
|
encrypted_function_args: None,
|
||||||
})),
|
})),
|
||||||
_ => Ok(None),
|
_ => Ok(None),
|
||||||
}
|
}
|
||||||
@@ -237,6 +272,7 @@ impl ToolRouter {
|
|||||||
tool_name,
|
tool_name,
|
||||||
call_id,
|
call_id,
|
||||||
payload,
|
payload,
|
||||||
|
..
|
||||||
} = call;
|
} = call;
|
||||||
|
|
||||||
// Keep the legacy ToolInvocation.turn field tied to the same request state until handlers migrate.
|
// Keep the legacy ToolInvocation.turn field tied to the same request state until handlers migrate.
|
||||||
|
|||||||
@@ -37,9 +37,25 @@ use super::ToolCallSource;
|
|||||||
use super::ToolRouter;
|
use super::ToolRouter;
|
||||||
use super::ToolRouterParams;
|
use super::ToolRouterParams;
|
||||||
use super::extension_tool_executors;
|
use super::extension_tool_executors;
|
||||||
|
use super::tool_log_payload;
|
||||||
|
|
||||||
struct ExtensionEchoContributor;
|
struct ExtensionEchoContributor;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tool_log_payload_redacts_plaintext_multi_agent_messages() {
|
||||||
|
let payload = ToolPayload::Function {
|
||||||
|
arguments: json!({"target": "/root/worker", "message": "secret message"}).to_string(),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
tool_log_payload(&payload, &ToolCallSource::DirectPlaintextMessage),
|
||||||
|
"[plaintext arguments]"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
tool_log_payload(&payload, &ToolCallSource::Direct),
|
||||||
|
payload.log_payload()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
impl codex_extension_api::ToolContributor for ExtensionEchoContributor {
|
impl codex_extension_api::ToolContributor for ExtensionEchoContributor {
|
||||||
fn tools(
|
fn tools(
|
||||||
&self,
|
&self,
|
||||||
@@ -135,6 +151,7 @@ async fn parallel_support_does_not_match_namespaced_local_tool_names() -> anyhow
|
|||||||
payload: ToolPayload::Function {
|
payload: ToolPayload::Function {
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
},
|
},
|
||||||
|
encrypted_function_args: None,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.expect("test session should expose a parallel shell-like tool");
|
.expect("test session should expose a parallel shell-like tool");
|
||||||
@@ -145,6 +162,7 @@ async fn parallel_support_does_not_match_namespaced_local_tool_names() -> anyhow
|
|||||||
payload: ToolPayload::Function {
|
payload: ToolPayload::Function {
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
},
|
},
|
||||||
|
encrypted_function_args: None,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -159,6 +177,7 @@ async fn build_tool_call_uses_namespace_for_registry_name() -> anyhow::Result<()
|
|||||||
name: tool_name.clone(),
|
name: tool_name.clone(),
|
||||||
namespace: Some("mcp__codex_apps__calendar".to_string()),
|
namespace: Some("mcp__codex_apps__calendar".to_string()),
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
|
encrypted_function_args: Some(Vec::new()),
|
||||||
call_id: "call-namespace".to_string(),
|
call_id: "call-namespace".to_string(),
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
})?
|
})?
|
||||||
@@ -169,6 +188,8 @@ async fn build_tool_call_uses_namespace_for_registry_name() -> anyhow::Result<()
|
|||||||
ToolName::namespaced("mcp__codex_apps__calendar", tool_name)
|
ToolName::namespaced("mcp__codex_apps__calendar", tool_name)
|
||||||
);
|
);
|
||||||
assert_eq!(call.call_id, "call-namespace");
|
assert_eq!(call.call_id, "call-namespace");
|
||||||
|
assert_eq!(call.encrypted_function_args, Some(Vec::new()));
|
||||||
|
assert_eq!(call.direct_source(), ToolCallSource::Direct);
|
||||||
match call.payload {
|
match call.payload {
|
||||||
ToolPayload::Function { arguments } => {
|
ToolPayload::Function { arguments } => {
|
||||||
assert_eq!(arguments, "{}");
|
assert_eq!(arguments, "{}");
|
||||||
@@ -202,6 +223,7 @@ async fn build_custom_tool_call_uses_namespace_for_registry_name() -> anyhow::Re
|
|||||||
payload: ToolPayload::Custom {
|
payload: ToolPayload::Custom {
|
||||||
input: "print('hello')".to_string(),
|
input: "print('hello')".to_string(),
|
||||||
},
|
},
|
||||||
|
encrypted_function_args: None,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -246,6 +268,7 @@ async fn mcp_parallel_support_uses_handler_data() -> anyhow::Result<()> {
|
|||||||
payload: ToolPayload::Function {
|
payload: ToolPayload::Function {
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
},
|
},
|
||||||
|
encrypted_function_args: None,
|
||||||
};
|
};
|
||||||
assert!(router.tool_supports_parallel(&call));
|
assert!(router.tool_supports_parallel(&call));
|
||||||
|
|
||||||
@@ -255,6 +278,7 @@ async fn mcp_parallel_support_uses_handler_data() -> anyhow::Result<()> {
|
|||||||
payload: ToolPayload::Function {
|
payload: ToolPayload::Function {
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
},
|
},
|
||||||
|
encrypted_function_args: None,
|
||||||
};
|
};
|
||||||
assert!(!router.tool_supports_parallel(&different_server_call));
|
assert!(!router.tool_supports_parallel(&different_server_call));
|
||||||
|
|
||||||
@@ -286,6 +310,7 @@ async fn tools_without_handlers_do_not_support_parallel() -> anyhow::Result<()>
|
|||||||
payload: ToolPayload::Function {
|
payload: ToolPayload::Function {
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
},
|
},
|
||||||
|
encrypted_function_args: None,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -438,6 +463,7 @@ async fn extension_tool_executors_are_model_visible_and_dispatchable() -> anyhow
|
|||||||
namespace: Some("extension/".to_string()),
|
namespace: Some("extension/".to_string()),
|
||||||
arguments: json!({ "message": "hello" }).to_string(),
|
arguments: json!({ "message": "hello" }).to_string(),
|
||||||
call_id: "call-extension".to_string(),
|
call_id: "call-extension".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
})?
|
})?
|
||||||
.expect("function_call should produce a tool call");
|
.expect("function_call should produce a tool call");
|
||||||
|
|||||||
@@ -61,9 +61,11 @@ impl ToolDispatchTrace {
|
|||||||
|
|
||||||
fn tool_dispatch_invocation(invocation: &ToolInvocation) -> Option<ToolDispatchInvocation> {
|
fn tool_dispatch_invocation(invocation: &ToolInvocation) -> Option<ToolDispatchInvocation> {
|
||||||
let requester = match &invocation.source {
|
let requester = match &invocation.source {
|
||||||
ToolCallSource::Direct => ToolDispatchRequester::Model {
|
ToolCallSource::Direct | ToolCallSource::DirectPlaintextMessage => {
|
||||||
model_visible_call_id: invocation.call_id.clone(),
|
ToolDispatchRequester::Model {
|
||||||
},
|
model_visible_call_id: invocation.call_id.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
ToolCallSource::CodeMode {
|
ToolCallSource::CodeMode {
|
||||||
cell_id,
|
cell_id,
|
||||||
runtime_tool_call_id,
|
runtime_tool_call_id,
|
||||||
@@ -91,9 +93,11 @@ fn tool_dispatch_result(
|
|||||||
result: &dyn ToolOutput,
|
result: &dyn ToolOutput,
|
||||||
) -> Option<ToolDispatchResult> {
|
) -> Option<ToolDispatchResult> {
|
||||||
match invocation.source {
|
match invocation.source {
|
||||||
ToolCallSource::Direct => Some(ToolDispatchResult::DirectResponse {
|
ToolCallSource::Direct | ToolCallSource::DirectPlaintextMessage => {
|
||||||
response_item: result.to_response_item(call_id, payload),
|
Some(ToolDispatchResult::DirectResponse {
|
||||||
}),
|
response_item: result.to_response_item(call_id, payload),
|
||||||
|
})
|
||||||
|
}
|
||||||
ToolCallSource::CodeMode { .. } => Some(ToolDispatchResult::CodeModeResponse {
|
ToolCallSource::CodeMode { .. } => Some(ToolDispatchResult::CodeModeResponse {
|
||||||
value: result.code_mode_result(payload),
|
value: result.code_mode_result(payload),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ fn response_item_records_turn_ttft_for_first_output_signals() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "call-1".to_string(),
|
call_id: "call-1".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
}
|
}
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -254,9 +254,18 @@ async fn openai_stateless_responses_requests_preserve_item_turn_metadata_across_
|
|||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn non_openai_responses_requests_include_item_ids_without_passthrough_metadata() {
|
async fn non_openai_responses_requests_include_item_ids_without_passthrough_metadata() {
|
||||||
let server = MockServer::start().await;
|
let server = MockServer::start().await;
|
||||||
let response_mock = mount_sse_once(
|
let mut private_function_call = ev_function_call("private-call", "unsupported_tool", "{}");
|
||||||
|
private_function_call["item"]["encrypted_function_args"] = json!(["message"]);
|
||||||
|
let response_mock = mount_sse_sequence(
|
||||||
&server,
|
&server,
|
||||||
sse(vec![ev_response_created("resp1"), ev_completed("resp1")]),
|
vec![
|
||||||
|
sse(vec![
|
||||||
|
ev_response_created("resp1"),
|
||||||
|
private_function_call,
|
||||||
|
ev_completed("resp1"),
|
||||||
|
]),
|
||||||
|
sse(vec![ev_response_created("resp2"), ev_completed("resp2")]),
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
let mut provider =
|
let mut provider =
|
||||||
@@ -289,7 +298,11 @@ async fn non_openai_responses_requests_include_item_ids_without_passthrough_meta
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
wait_for_event(&codex, |event| matches!(event, EventMsg::TurnComplete(_))).await;
|
||||||
|
|
||||||
let body = response_mock.single_request().body_json();
|
let body = response_mock
|
||||||
|
.requests()
|
||||||
|
.pop()
|
||||||
|
.expect("follow-up request")
|
||||||
|
.body_json();
|
||||||
let input = body["input"]
|
let input = body["input"]
|
||||||
.as_array()
|
.as_array()
|
||||||
.expect("request should include input items");
|
.expect("request should include input items");
|
||||||
@@ -300,6 +313,10 @@ async fn non_openai_responses_requests_include_item_ids_without_passthrough_meta
|
|||||||
.is_none(),
|
.is_none(),
|
||||||
"input item should omit internal chat message metadata passthrough: {item}"
|
"input item should omit internal chat message metadata passthrough: {item}"
|
||||||
);
|
);
|
||||||
|
assert!(
|
||||||
|
item.get("encrypted_function_args").is_none(),
|
||||||
|
"input item should omit private encrypted function metadata: {item}"
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
item.get("id").and_then(serde_json::Value::as_str).is_some(),
|
item.get("id").and_then(serde_json::Value::as_str).is_some(),
|
||||||
"input item should include a generated ID: {item}"
|
"input item should include a generated ID: {item}"
|
||||||
@@ -508,6 +525,7 @@ async fn synthetic_call_output_id_is_stable_across_resumes() -> anyhow::Result<(
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: function_call_id.to_string(),
|
call_id: function_call_id.to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -1169,6 +1187,7 @@ async fn resume_replays_image_tool_outputs_with_detail() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{\"path\":\"/tmp/example.png\"}".to_string(),
|
arguments: "{\"path\":\"/tmp/example.png\"}".to_string(),
|
||||||
call_id: function_call_id.to_string(),
|
call_id: function_call_id.to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -3276,6 +3295,7 @@ async fn azure_responses_request_includes_store_and_prefixed_item_ids() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".into(),
|
arguments: "{}".into(),
|
||||||
call_id: "function-call-id".into(),
|
call_id: "function-call-id".into(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
});
|
});
|
||||||
prompt.input.push(ResponseItem::FunctionCallOutput {
|
prompt.input.push(ResponseItem::FunctionCallOutput {
|
||||||
|
|||||||
@@ -1400,8 +1400,10 @@ async fn spawned_multi_agent_v2_child_inherits_parent_developer_context() -> Res
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test_case(false; "encrypted")]
|
||||||
|
#[test_case(true; "plaintext")]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn encrypted_multi_agent_v2_spawn_sends_agent_message_to_child() -> Result<()> {
|
async fn multi_agent_v2_spawn_sends_agent_message_to_child(plaintext: bool) -> Result<()> {
|
||||||
let output: &'static Mutex<Vec<u8>> = Box::leak(Box::new(Mutex::new(Vec::new())));
|
let output: &'static Mutex<Vec<u8>> = Box::leak(Box::new(Mutex::new(Vec::new())));
|
||||||
let subscriber = tracing_subscriber::fmt()
|
let subscriber = tracing_subscriber::fmt()
|
||||||
.with_ansi(false)
|
.with_ansi(false)
|
||||||
@@ -1411,22 +1413,30 @@ async fn encrypted_multi_agent_v2_spawn_sends_agent_message_to_child() -> Result
|
|||||||
let _guard = tracing::subscriber::set_default(subscriber);
|
let _guard = tracing::subscriber::set_default(subscriber);
|
||||||
|
|
||||||
let server = start_mock_server().await;
|
let server = start_mock_server().await;
|
||||||
let encrypted_message = "opaque-encrypted-message";
|
let message = if plaintext {
|
||||||
|
"plaintext delegated task"
|
||||||
|
} else {
|
||||||
|
"opaque-encrypted-message"
|
||||||
|
};
|
||||||
let spawn_args = serde_json::to_string(&json!({
|
let spawn_args = serde_json::to_string(&json!({
|
||||||
"message": encrypted_message,
|
"message": message,
|
||||||
"task_name": "worker",
|
"task_name": "worker",
|
||||||
}))?;
|
}))?;
|
||||||
|
let mut spawn_event = ev_function_call_with_namespace(
|
||||||
|
SPAWN_CALL_ID,
|
||||||
|
MULTI_AGENT_V2_NAMESPACE,
|
||||||
|
"spawn_agent",
|
||||||
|
&spawn_args,
|
||||||
|
);
|
||||||
|
if plaintext {
|
||||||
|
spawn_event["item"]["encrypted_function_args"] = json!([]);
|
||||||
|
}
|
||||||
mount_sse_once_match(
|
mount_sse_once_match(
|
||||||
&server,
|
&server,
|
||||||
|req: &wiremock::Request| body_contains(req, TURN_1_PROMPT),
|
|req: &wiremock::Request| body_contains(req, TURN_1_PROMPT),
|
||||||
sse(vec![
|
sse(vec![
|
||||||
ev_response_created("resp-parent-1"),
|
ev_response_created("resp-parent-1"),
|
||||||
ev_function_call_with_namespace(
|
spawn_event,
|
||||||
SPAWN_CALL_ID,
|
|
||||||
MULTI_AGENT_V2_NAMESPACE,
|
|
||||||
"spawn_agent",
|
|
||||||
&spawn_args,
|
|
||||||
),
|
|
||||||
ev_completed("resp-parent-1"),
|
ev_completed("resp-parent-1"),
|
||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
@@ -1440,7 +1450,7 @@ async fn encrypted_multi_agent_v2_spawn_sends_agent_message_to_child() -> Result
|
|||||||
]),
|
]),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
mount_sse_once_match(
|
let parent_request_log = mount_sse_once_match(
|
||||||
&server,
|
&server,
|
||||||
|req: &wiremock::Request| {
|
|req: &wiremock::Request| {
|
||||||
body_contains(req, SPAWN_CALL_ID) && !request_has_input_type(req, "agent_message")
|
body_contains(req, SPAWN_CALL_ID) && !request_has_input_type(req, "agent_message")
|
||||||
@@ -1484,6 +1494,25 @@ async fn encrypted_multi_agent_v2_spawn_sends_agent_message_to_child() -> Result
|
|||||||
}
|
}
|
||||||
sleep(Duration::from_millis(10)).await;
|
sleep(Duration::from_millis(10)).await;
|
||||||
};
|
};
|
||||||
|
let content = if plaintext {
|
||||||
|
vec![json!({
|
||||||
|
"type": "input_text",
|
||||||
|
"text": format!(
|
||||||
|
"Message Type: NEW_TASK\nTask name: /root/worker\nSender: /root\nPayload:\n{message}"
|
||||||
|
),
|
||||||
|
})]
|
||||||
|
} else {
|
||||||
|
vec![
|
||||||
|
json!({
|
||||||
|
"type": "input_text",
|
||||||
|
"text": "Message Type: NEW_TASK\nTask name: /root/worker\nSender: /root\nPayload:\n",
|
||||||
|
}),
|
||||||
|
json!({
|
||||||
|
"type": "encrypted_content",
|
||||||
|
"encrypted_content": message,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
};
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
strip_response_item_ids_from_json(strip_metadata_from_json(Value::Array(
|
strip_response_item_ids_from_json(strip_metadata_from_json(Value::Array(
|
||||||
child_request.inputs_of_type("agent_message"),
|
child_request.inputs_of_type("agent_message"),
|
||||||
@@ -1492,18 +1521,20 @@ async fn encrypted_multi_agent_v2_spawn_sends_agent_message_to_child() -> Result
|
|||||||
"type": "agent_message",
|
"type": "agent_message",
|
||||||
"author": "/root",
|
"author": "/root",
|
||||||
"recipient": "/root/worker",
|
"recipient": "/root/worker",
|
||||||
"content": [
|
"content": content,
|
||||||
{
|
|
||||||
"type": "input_text",
|
|
||||||
"text": "Message Type: NEW_TASK\nTask name: /root/worker\nSender: /root\nPayload:\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "encrypted_content",
|
|
||||||
"encrypted_content": encrypted_message,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})])
|
})])
|
||||||
);
|
);
|
||||||
|
if plaintext {
|
||||||
|
assert!(
|
||||||
|
parent_request_log.requests().into_iter().any(|request| {
|
||||||
|
request.input().iter().any(|item| {
|
||||||
|
item["call_id"].as_str() == Some(SPAWN_CALL_ID)
|
||||||
|
&& item["encrypted_function_args"] == json!([])
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
"plaintext function-call metadata should survive replay"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let child_thread_id = test
|
let child_thread_id = test
|
||||||
.thread_manager
|
.thread_manager
|
||||||
@@ -1530,7 +1561,8 @@ async fn encrypted_multi_agent_v2_spawn_sends_agent_message_to_child() -> Result
|
|||||||
.expect("spawn send event");
|
.expect("spawn send event");
|
||||||
assert!(send.contains(&format!("sender_thread_id={root_thread_id}")));
|
assert!(send.contains(&format!("sender_thread_id={root_thread_id}")));
|
||||||
assert!(send.contains(&format!("receiver_thread_id={child_thread_id}")));
|
assert!(send.contains(&format!("receiver_thread_id={child_thread_id}")));
|
||||||
assert!(send.contains(&format!("content=\"{encrypted_message}\"")));
|
let logged_message = if plaintext { "[plaintext]" } else { message };
|
||||||
|
assert!(send.contains(&format!("content=\"{logged_message}\"")));
|
||||||
|
|
||||||
let communication_id = log_field(send, "communication_id").expect("communication ID");
|
let communication_id = log_field(send, "communication_id").expect("communication ID");
|
||||||
logs.lines()
|
logs.lines()
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ async fn recent_image_fallback_selects_newest_images_in_chronological_order() {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "mcp-call".to_string(),
|
call_id: "mcp-call".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
ResponseItem::FunctionCallOutput {
|
ResponseItem::FunctionCallOutput {
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ mod tests {
|
|||||||
namespace: None,
|
namespace: None,
|
||||||
arguments: "{}".to_string(),
|
arguments: "{}".to_string(),
|
||||||
call_id: "call-1".to_string(),
|
call_id: "call-1".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
},
|
},
|
||||||
previous_assistant,
|
previous_assistant,
|
||||||
|
|||||||
@@ -882,6 +882,9 @@ pub enum ResponseItem {
|
|||||||
// JSON, not as an already‑parsed object. We keep it as a raw string here and let
|
// JSON, not as an already‑parsed object. We keep it as a raw string here and let
|
||||||
// Session::handle_function_call parse it into a Value.
|
// Session::handle_function_call parse it into a Value.
|
||||||
arguments: String,
|
arguments: String,
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
|
#[ts(optional)]
|
||||||
|
encrypted_function_args: Option<Vec<String>>,
|
||||||
call_id: String,
|
call_id: String,
|
||||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||||
#[ts(optional)]
|
#[ts(optional)]
|
||||||
@@ -2892,12 +2895,29 @@ mod tests {
|
|||||||
name: "mcp__codex_apps__gmail_get_recent_emails".to_string(),
|
name: "mcp__codex_apps__gmail_get_recent_emails".to_string(),
|
||||||
namespace: Some("mcp__codex_apps__gmail".to_string()),
|
namespace: Some("mcp__codex_apps__gmail".to_string()),
|
||||||
arguments: "{\"top_k\":5}".to_string(),
|
arguments: "{\"top_k\":5}".to_string(),
|
||||||
|
encrypted_function_args: None,
|
||||||
call_id: "call-1".to_string(),
|
call_id: "call-1".to_string(),
|
||||||
internal_chat_message_metadata_passthrough: None,
|
internal_chat_message_metadata_passthrough: None,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn function_call_preserves_empty_encrypted_function_args() {
|
||||||
|
let value = serde_json::json!({
|
||||||
|
"type": "function_call",
|
||||||
|
"name": "spawn_agent",
|
||||||
|
"namespace": "collaboration",
|
||||||
|
"arguments": "{\"message\":\"hello\",\"task_name\":\"worker\"}",
|
||||||
|
"encrypted_function_args": [],
|
||||||
|
"call_id": "call-1",
|
||||||
|
});
|
||||||
|
let item: ResponseItem =
|
||||||
|
serde_json::from_value(value.clone()).expect("function_call should deserialize");
|
||||||
|
|
||||||
|
assert_eq!(serde_json::to_value(item).expect("serialize item"), value);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_command_prefix_list_sorts_by_len_then_total_len_then_alphabetical() {
|
fn render_command_prefix_list_sorts_by_len_then_total_len_then_alphabetical() {
|
||||||
let prefixes = vec![
|
let prefixes = vec![
|
||||||
|
|||||||
Reference in new issue
Block a user