Add audio variants to user input protocols (#33923)

## What changed

- Add remote and local audio variants to core and app-server user inputs, including generated JSON and TypeScript schemas.
- Preserve audio fields when converting app-server inputs and response content into thread items.
- Keep unsupported audio out of model requests, TUI rendering, text extraction, and truncated tool output, using explicit text placeholders where audio is submitted as a model input.

## Testing

- Cover app-server media conversion, unsupported-input placeholders, TUI omission, and tool-output truncation.

GitOrigin-RevId: 16152bf3af674575ce569764a8b472e032e7569d
This commit is contained in:
nhamidi-oai
2026-07-17 23:14:37 +00:00
committed by copyberry
parent e98765c212
commit 97dbd01a3d
49 changed files with 1330 additions and 33 deletions
+4
View File
@@ -750,6 +750,10 @@ pub(crate) fn render_input_preview(input: &[UserInput]) -> String {
UserInput::LocalImage { path, .. } => {
format!("[local_image:{}]", path.display())
}
UserInput::Audio { .. } => "[audio]".to_string(),
UserInput::LocalAudio { path } => {
format!("[local_audio:{}]", path.display())
}
UserInput::Skill { name, path, .. } => {
format!("[skill:${name}]({})", path.display())
}
+5 -3
View File
@@ -252,7 +252,7 @@ fn has_subagent_notification(history_items: &[ResponseItem]) -> bool {
ContentItem::InputText { text } | ContentItem::OutputText { text } => {
SubagentNotification::matches_text(text)
}
ContentItem::InputImage { .. } => false,
ContentItem::InputImage { .. } | ContentItem::InputAudio { .. } => false,
})
})
}
@@ -267,7 +267,7 @@ fn history_contains_text(history_items: &[ResponseItem], needle: &str) -> bool {
ContentItem::InputText { text } | ContentItem::OutputText { text } => {
text.contains(needle)
}
ContentItem::InputImage { .. } => false,
ContentItem::InputImage { .. } | ContentItem::InputAudio { .. } => false,
})
})
}
@@ -290,7 +290,9 @@ fn history_contains_assistant_inter_agent_communication(
.as_ref()
== Some(expected)
}
ContentItem::InputText { .. } | ContentItem::InputImage { .. } => false,
ContentItem::InputText { .. }
| ContentItem::InputImage { .. }
| ContentItem::InputAudio { .. } => false,
})
})
}
+1 -1
View File
@@ -484,7 +484,7 @@ pub fn content_items_to_text(content: &[ContentItem]) -> Option<String> {
pieces.push(text.as_str());
}
}
ContentItem::InputImage { .. } => {}
ContentItem::InputImage { .. } | ContentItem::InputAudio { .. } => {}
}
}
if pieces.is_empty() {
+4 -2
View File
@@ -516,7 +516,7 @@ fn message_text_token_count(item: &ResponseItem) -> usize {
ContentItem::InputText { text } | ContentItem::OutputText { text } => {
approx_token_count(text)
}
ContentItem::InputImage { .. } => 0,
ContentItem::InputImage { .. } | ContentItem::InputAudio { .. } => 0,
})
.sum()
}
@@ -556,7 +556,9 @@ fn truncate_message_text_to_token_budget(
truncated_content.push(content_item);
}
}
ContentItem::InputImage { .. } => truncated_content.push(content_item),
ContentItem::InputImage { .. } | ContentItem::InputAudio { .. } => {
truncated_content.push(content_item);
}
}
}
+5
View File
@@ -113,6 +113,11 @@ fn parse_user_message(message: &[ContentItem]) -> Option<UserMessageItem> {
detail: *detail,
});
}
ContentItem::InputAudio { audio_url } => {
content.push(UserInput::Audio {
audio_url: audio_url.clone(),
});
}
ContentItem::OutputText { text } => {
warn!("Output text in user message: {}", text);
}
+1 -1
View File
@@ -345,7 +345,7 @@ fn response_item_contains_message_text(item: &ResponseItem, needle: &str) -> boo
};
content.iter().any(|item| match item {
ContentItem::InputText { text } | ContentItem::OutputText { text } => text.contains(needle),
ContentItem::InputImage { .. } => false,
ContentItem::InputImage { .. } | ContentItem::InputAudio { .. } => false,
})
}
+3 -1
View File
@@ -844,7 +844,9 @@ mod tests {
.iter()
.map(|item| match item {
ContentItem::InputText { text } => text.as_str(),
ContentItem::InputImage { .. } | ContentItem::OutputText { .. } => {
ContentItem::InputImage { .. }
| ContentItem::InputAudio { .. }
| ContentItem::OutputText { .. } => {
panic!("expected input text content, got {item:?}")
}
})
@@ -559,10 +559,14 @@ fn create_collab_input_items_schema() -> JsonSchema {
"image_url".to_string(),
JsonSchema::string(Some("Image URL when type is image.".to_string())),
),
(
"audio_url".to_string(),
JsonSchema::string(Some("Audio URL when type is audio.".to_string())),
),
(
"path".to_string(),
JsonSchema::string(Some(
"Path when type is local_image/skill, or structured mention target such as app://<connector-id> or plugin://<plugin-name>@<marketplace-name> when type is mention."
"Path when type is local_image/local_audio/skill, or structured mention target such as app://<connector-id> or plugin://<plugin-name>@<marketplace-name> when type is mention."
.to_string(),
)),
),