mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Add opt-in retention for turn tool outputs (#52686)
## What changed Add an optional `retain` flag to `TurnToolOutput` for `turn/start`. When set to `true`, pass the output to the core with a retention annotation to request retention in the thread's model history. The flag defaults to `false`, preserving existing behavior. Update the generated JSON schemas, TypeScript types, and Python bindings to expose the flag. ## Testing Add an integration test verifying that a retained tool output reaches the model and preserves client-authored sender metadata in persisted history, including the receiver message ID. GitOrigin-RevId: 52893a8fcb308f26e77ba8f0bee9990073b26d54
This commit is contained in:
@@ -5928,6 +5928,10 @@
|
||||
},
|
||||
"output": {
|
||||
"$ref": "#/definitions/FunctionCallOutputBody"
|
||||
},
|
||||
"retain": {
|
||||
"description": "Requests retention of this output in the thread's model history.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
+4
@@ -26301,6 +26301,10 @@
|
||||
},
|
||||
"output": {
|
||||
"$ref": "#/definitions/v2/FunctionCallOutputBody"
|
||||
},
|
||||
"retain": {
|
||||
"description": "Requests retention of this output in the thread's model history.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
+4
@@ -24015,6 +24015,10 @@
|
||||
},
|
||||
"output": {
|
||||
"$ref": "#/definitions/FunctionCallOutputBody"
|
||||
},
|
||||
"retain": {
|
||||
"description": "Requests retention of this output in the thread's model history.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@@ -533,6 +533,10 @@
|
||||
},
|
||||
"output": {
|
||||
"$ref": "#/definitions/FunctionCallOutputBody"
|
||||
},
|
||||
"retain": {
|
||||
"description": "Requests retention of this output in the thread's model history.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -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 { FunctionCallOutputBody } from "../FunctionCallOutputBody";
|
||||
|
||||
export type TurnToolOutput = { name: string, namespace: string | null, output: FunctionCallOutputBody, };
|
||||
export type TurnToolOutput = { name: string, namespace: string | null, output: FunctionCallOutputBody,
|
||||
/**
|
||||
* Requests retention of this output in the thread's model history.
|
||||
*/
|
||||
retain?: boolean, };
|
||||
|
||||
@@ -157,6 +157,9 @@ pub struct TurnToolOutput {
|
||||
pub name: String,
|
||||
pub namespace: Option<String>,
|
||||
pub output: FunctionCallOutputBody,
|
||||
/// Requests retention of this output in the thread's model history.
|
||||
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
||||
pub retain: bool,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
|
||||
@@ -15,6 +15,8 @@ use codex_protocol::protocol::AdditionalContextKind as CoreAdditionalContextKind
|
||||
use codex_protocol::protocol::TurnEnvironmentRequests;
|
||||
use codex_protocol::protocol::TurnSettingsUpdate;
|
||||
use codex_protocol::protocol::TurnSettingsUpdateOutcome;
|
||||
use codex_protocol::turn_input::AnnotatedResponseItem;
|
||||
use codex_protocol::turn_input::ResponseItemAnnotations;
|
||||
use codex_skills::system_cache_root_dir;
|
||||
|
||||
use crate::image_url::REMOTE_IMAGE_URL_ERROR;
|
||||
@@ -608,7 +610,14 @@ impl TurnRequestProcessor {
|
||||
internal_chat_message_metadata_passthrough: None,
|
||||
};
|
||||
validate_response_item_image_urls(std::slice::from_ref(&item))?;
|
||||
TurnInput::ResponseItem(item)
|
||||
if tool_output.retain {
|
||||
TurnInput::AnnotatedResponseItem(AnnotatedResponseItem {
|
||||
item,
|
||||
annotations: ResponseItemAnnotations { retain: true },
|
||||
})
|
||||
} else {
|
||||
TurnInput::ResponseItem(item)
|
||||
}
|
||||
} else {
|
||||
TurnInput::UserInput {
|
||||
content: params
|
||||
|
||||
@@ -297,6 +297,7 @@ async fn thread_inject_items_adds_raw_response_items_to_thread_history(
|
||||
thread_id: thread.id.clone(),
|
||||
input: Vec::new(),
|
||||
tool_output: Some(Box::new(TurnToolOutput {
|
||||
retain: false,
|
||||
name: "send_message_to_thread".to_string(),
|
||||
namespace: Some("codex_app".to_string()),
|
||||
output: FunctionCallOutputBody::Text("Start a delegated turn.".to_string()),
|
||||
@@ -655,3 +656,89 @@ fn response_item_text_position(items: &[Value], needle: &str) -> Option<usize> {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn turn_tool_output_retention_preserves_sender_metadata() -> Result<()> {
|
||||
let server = responses::start_mock_server().await;
|
||||
let response_mock = responses::mount_sse_once(
|
||||
&server,
|
||||
responses::sse(vec![responses::ev_completed("delegated-turn")]),
|
||||
)
|
||||
.await;
|
||||
let codex_home = TempDir::new()?;
|
||||
MockResponsesConfig::new(&server.uri()).write(codex_home.path())?;
|
||||
let mut app = TestAppServer::builder()
|
||||
.with_codex_home(codex_home.path())
|
||||
.build()
|
||||
.await?;
|
||||
app.initialize().await?;
|
||||
let request = app
|
||||
.send_thread_start_request_with_auto_env(ThreadStartParams::default())
|
||||
.await?;
|
||||
let ThreadStartResponse { thread, .. } = app.read_response(request).await?;
|
||||
let source_thread_id = ThreadId::new();
|
||||
let output = format!(
|
||||
"<codex_delegation>\n <source_thread_id>{source_thread_id}</source_thread_id>\n <input>Investigate the failing tests.</input>\n</codex_delegation>"
|
||||
);
|
||||
let request = app
|
||||
.send_turn_start_request(TurnStartParams {
|
||||
thread_id: thread.id.clone(),
|
||||
input: Vec::new(),
|
||||
tool_output: Some(Box::new(TurnToolOutput {
|
||||
name: "send_message_to_thread".to_string(),
|
||||
namespace: Some("codex_app".to_string()),
|
||||
output: FunctionCallOutputBody::Text(output.clone()),
|
||||
retain: true,
|
||||
})),
|
||||
..Default::default()
|
||||
})
|
||||
.await?;
|
||||
let _: TurnStartResponse = app.read_response(request).await?;
|
||||
app.read_stream_until_notification_message("turn/completed")
|
||||
.await?;
|
||||
let model_outputs = response_mock
|
||||
.single_request()
|
||||
.inputs_of_type("function_call_output")
|
||||
.into_iter()
|
||||
.map(responses::strip_metadata_from_json)
|
||||
.map(strip_response_item_ids_from_json)
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
model_outputs,
|
||||
vec![json!({
|
||||
"type": "function_call_output",
|
||||
"name": "send_message_to_thread",
|
||||
"namespace": "codex_app",
|
||||
"output": output,
|
||||
})]
|
||||
);
|
||||
let rollout_path = thread.path.as_ref().context("thread path missing")?;
|
||||
let InitialHistory::Resumed(history) =
|
||||
RolloutRecorder::get_rollout_history(rollout_path).await?
|
||||
else {
|
||||
panic!("expected persisted history");
|
||||
};
|
||||
let envelope = history
|
||||
.history
|
||||
.iter()
|
||||
.find_map(|item| match item {
|
||||
RolloutItem::ResponseItem(envelope)
|
||||
if matches!(envelope.item, ResponseItem::FunctionCallOutput { .. }) =>
|
||||
{
|
||||
Some(envelope)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.context("delegation output should be saved")?;
|
||||
let metadata = envelope.metadata.as_ref().context("output metadata")?;
|
||||
assert!(metadata.client_authored);
|
||||
let sender_messages = metadata
|
||||
.sender_user_messages
|
||||
.as_ref()
|
||||
.context("sender context must be preserved")?;
|
||||
assert_eq!(
|
||||
sender_messages.receiver_message_id,
|
||||
envelope.item.id().unwrap().to_string()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1243,6 +1243,7 @@ async fn start_turn(
|
||||
// Older app-server/TUI versions are intentionally unsupported:
|
||||
// preserving tool authority takes precedence over legacy fallback.
|
||||
tool_output: Some(Box::new(TurnToolOutput {
|
||||
retain: false,
|
||||
name: tool.to_string(),
|
||||
namespace: Some(NAMESPACE.to_string()),
|
||||
output: FunctionCallOutputBody::Text(prompt),
|
||||
|
||||
@@ -11832,6 +11832,10 @@ class TurnToolOutput(BaseModel):
|
||||
name: str
|
||||
namespace: str | None = None
|
||||
output: FunctionCallOutputBody
|
||||
retain: Annotated[
|
||||
bool | None,
|
||||
Field(description="Requests retention of this output in the thread's model history."),
|
||||
] = None
|
||||
|
||||
|
||||
class TurnsPage(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user