Support standalone named function call outputs (#39782)

## Why

External tool events may need to enter thread history without a preceding function call and therefore do not have a `call_id`.

## What changed

- Allow `function_call_output` items to omit `call_id` and carry optional `name` and `namespace` fields.
- Preserve named standalone outputs during history normalization and agent forks while retaining existing pairing behavior for outputs with a `call_id`.
- Accept, persist, and forward these outputs through `thread/inject_items`, and update the app-server schemas and documentation.

## Testing

- Cover paired and standalone JSON round trips, history normalization, agent forks, and injected thread history.

GitOrigin-RevId: a3258163a7dc93777c7c3023116fe204819bdbb0
This commit is contained in:
rka-oai authored and copyberry committed 2026-08-20 19:19:41 +00:00
1 parent 854cbb2fd4
commit 763787d061
42 files changed
+473 -114

No files matched your search

@@ -918,7 +918,9 @@ max_recent_non_user_entries = 8
},
ResponseItem::FunctionCallOutput {
id: None,
call_id: "previous-call".to_owned(),
call_id: Some("previous-call".to_owned()),
name: None,
namespace: None,
output: FunctionCallOutputPayload::from_text("README.md".to_owned()),
internal_chat_message_metadata_passthrough: None,
},
@@ -1174,7 +1176,9 @@ async fn contributor_includes_configured_transcript_images() -> Result<()> {
},
ResponseItem::FunctionCallOutput {
id: None,
call_id: "previous-call".to_owned(),
call_id: Some("previous-call".to_owned()),
name: None,
namespace: None,
output: FunctionCallOutputPayload::from_content_items(vec![
FunctionCallOutputContentItem::InputText {
text: "Screenshot captured.".to_owned(),
@@ -1386,7 +1390,9 @@ async fn contributor_samples_tool_calls_with_the_existing_luna_pool() -> Result<
},
ResponseItem::FunctionCallOutput {
id: None,
call_id: "previous-call".to_owned(),
call_id: Some("previous-call".to_owned()),
name: None,
namespace: None,
output: FunctionCallOutputPayload::from_text("README.md".to_owned()),
internal_chat_message_metadata_passthrough: None,
},
@@ -1662,7 +1668,9 @@ async fn contributor_sends_compacted_conversation_history_to_luna() -> Result<()
},
ResponseItem::FunctionCallOutput {
id: None,
call_id,
call_id: Some(call_id),
name: None,
namespace: None,
output: FunctionCallOutputPayload::from_text(format!(
"result evidence {index}: {}",
"signal ".repeat(1_000)
@@ -228,7 +228,9 @@ impl TranscriptConfig {
(format!("tool {name} call"), arguments.clone())
}
ResponseItem::FunctionCallOutput {
call_id, output, ..
call_id: Some(call_id),
output,
..
}
| ResponseItem::CustomToolCallOutput {
call_id, output, ..
@@ -287,6 +289,7 @@ impl TranscriptConfig {
("tool web_search call".to_owned(), text)
}
ResponseItem::AdditionalTools { .. }
| ResponseItem::FunctionCallOutput { call_id: None, .. }
| ResponseItem::ImageGenerationCall { .. }
| ResponseItem::ToolSearchCall { .. }
| ResponseItem::ToolSearchOutput { .. }
@@ -40,7 +40,9 @@ fn transcript_keeps_conversation_and_configured_sources() {
},
ResponseItem::FunctionCallOutput {
id: None,
call_id: "call-1".to_string(),
call_id: Some("call-1".to_string()),
name: None,
namespace: None,
output: FunctionCallOutputPayload::from_text("Workspace inspected.".to_string()),
internal_chat_message_metadata_passthrough: None,
},
@@ -459,7 +461,9 @@ fn transcript_truncates_tool_results_using_standard_budget() {
},
ResponseItem::FunctionCallOutput {
id: None,
call_id: "call-1".to_owned(),
call_id: Some("call-1".to_owned()),
name: None,
namespace: None,
output: FunctionCallOutputPayload::from_text(output),
internal_chat_message_metadata_passthrough: None,
},
@@ -596,7 +600,9 @@ fn transcript_omits_media_payloads_and_keeps_readable_content() {
},
ResponseItem::FunctionCallOutput {
id: None,
call_id: "call-1".to_string(),
call_id: Some("call-1".to_string()),
name: None,
namespace: None,
output: FunctionCallOutputPayload::from_content_items(vec![
FunctionCallOutputContentItem::InputText {
text: "Screenshot captured.".to_string(),