Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion crates/agentic-server-core/src/executor/compaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ fn item_has_meaningful_context(item: &InputItem) -> bool {
!text.text.trim().is_empty()
}
InputContent::InputImage(image) => image.image_url.as_deref().is_some_and(|url| !url.trim().is_empty()),
InputContent::Unknown => false,
// Message files are rejected during typed input validation.
InputContent::InputFile(_) | InputContent::Unknown => false,
}),
},
InputItem::FunctionCall(call) => !call.name.trim().is_empty() || !call.arguments.trim().is_empty(),
Expand Down
32 changes: 30 additions & 2 deletions crates/agentic-server-core/src/executor/rehydrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,35 @@ use crate::executor::request::{ExecutionContext, RequestContext};
use crate::storage::InOutItem;
use crate::tool::ToolError;
use crate::types::io::{
InputItem, ReasoningOutput, ReasoningTextContent, ResponsesInput, resolve_tool_choice, resolve_tools,
InputContent, InputItem, InputMessageContent, ReasoningOutput, ReasoningTextContent, ResponsesInput,
resolve_tool_choice, resolve_tools,
};
use crate::types::request_response::RequestPayload;
use crate::utils::uuid7_str;

/// Reject unsupported message files on typed paths, including restored history.
///
/// Keep this out of deserialization: eligible raw proxy requests must retain their
/// original bytes and leave support decisions to the upstream. Structured tool
/// call outputs have a separate content type and are deliberately not rejected.
pub(super) fn validate_message_files(input: &ResponsesInput) -> ExecutorResult<()> {
let ResponsesInput::Items(items) = input else {
return Ok(());
};
let has_file = items.iter().any(|item| {
matches!(item, InputItem::Message(message)
if matches!(&message.content, InputMessageContent::Parts(parts)
if parts.iter().any(|part| matches!(part, InputContent::InputFile(_)))))
});
if has_file {
return Err(ExecutorError::InvalidRequest(
"input_file content in messages is not supported by the typed Responses executor; provide input_text or input_image content instead"
.to_owned(),
));
}
Ok(())
}

fn has_plaintext_reasoning(reasoning: &ReasoningOutput) -> bool {
reasoning.content.iter().any(|content| !content.text.is_empty())
}
Expand Down Expand Up @@ -93,11 +117,14 @@ pub(super) fn prepare_reasoning_for_vllm(input: &mut ResponsesInput) -> Executor
/// - no ids: forward only the new input
///
/// # Errors
/// Returns [`ExecutorError`] if storage is unavailable or a referenced ID does not exist.
/// Returns [`ExecutorError`] if storage is unavailable, a referenced ID does not exist,
/// or the new/resolved input contains unsupported message files.
pub async fn rehydrate_conversation(
request: RequestPayload,
exec_ctx: &ExecutionContext,
) -> ExecutorResult<RequestContext> {
// Fail before storage work for new files; check again once history is resolved.
validate_message_files(&request.input)?;
let response_id = uuid7_str("resp_");
let new_input_items: Vec<InputItem> = Vec::from(&request.input);

Expand Down Expand Up @@ -126,6 +153,7 @@ pub async fn rehydrate_conversation(
ctx.enriched_request.input = ResponsesInput::Items(ctx.new_input_items.clone());
}

validate_message_files(&ctx.enriched_request.input)?;
Ok(ctx)
}

Expand Down
5 changes: 4 additions & 1 deletion crates/agentic-server-core/src/executor/upstream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::executor::gateway::{
};
use crate::executor::gateway_accumulator::{GatewayStreamAccumulator, StreamEvent, emit_sse_frame};
use crate::executor::inference::{call_inference, fetch_response_json};
use crate::executor::rehydrate::validate_message_files;
use crate::executor::request::{ExecutionContext, RequestContext};
use crate::tool::ToolRegistry;
use crate::types::io::OutputItem;
Expand All @@ -39,8 +40,10 @@ pub(super) struct StreamPayload {
/// fields removed.
///
/// # Errors
/// A tool-configuration or serialization failure.
/// Unsupported message files, a tool-configuration error, or a serialization failure.
pub fn upstream_request(ctx: &RequestContext, stream: bool) -> ExecutorResult<String> {
// Composable callers may supply RequestContext without the rehydration step.
validate_message_files(&ctx.enriched_request.input)?;
let request = ctx.enriched_request.to_upstream_request(stream)?;
serialize_to_string(&request).map_err(ExecutorError::JsonError)
}
Expand Down
2 changes: 2 additions & 0 deletions crates/agentic-server-core/src/types/io/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ pub struct InputFileContent {
pub enum InputContent {
InputText(InputTextContent),
InputImage(InputImageContent),
/// Preserved on the wire; support is validated after the routing decision.
InputFile(InputFileContent),
/// Assistant output text in rehydrated history.
OutputText(InputTextContent),
/// Reasoning step text in rehydrated history.
Expand Down
4 changes: 2 additions & 2 deletions crates/agentic-server-core/src/types/io/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1309,8 +1309,8 @@ mod tests {
"role": "user",
"content": [
{
"type": "input_file",
"file_id": "file_1"
"type": "future_content",
"payload": "future_value"
}
]
});
Expand Down
Loading