diff --git a/server-rs/crates/platform-llm/src/lib.rs b/server-rs/crates/platform-llm/src/lib.rs index f759cc8c0..d21c888d5 100644 --- a/server-rs/crates/platform-llm/src/lib.rs +++ b/server-rs/crates/platform-llm/src/lib.rs @@ -2276,7 +2276,7 @@ where accumulation.responses_output = output; } for (slot, item) in output_items { - upsert_responses_output_item(&mut accumulation.responses_output, slot, item); + upsert_responses_output_item(&mut accumulation.responses_output, slot, item)?; } if is_completion { @@ -3744,12 +3744,19 @@ fn upsert_responses_output_item( output: &mut Vec, slot: u64, item: serde_json::Value, -) { +) -> Result<(), LlmError> { + const MAX_RESPONSES_OUTPUT_SLOT: u64 = 4096; + if slot >= MAX_RESPONSES_OUTPUT_SLOT { + return Err(LlmError::Deserialize(format!( + "解析 LLM Responses SSE 事件失败:output_index 超出安全上限({slot} >= {MAX_RESPONSES_OUTPUT_SLOT})" + ))); + } let index = slot as usize; if index >= output.len() { output.resize(index + 1, serde_json::Value::Null); } output[index] = item; + Ok(()) } fn patch_responses_output_arguments(output: &mut [serde_json::Value], slot: u64, arguments: &str) { @@ -4103,6 +4110,22 @@ mod tests { use super::*; + #[test] + fn responses_output_slot_safety_fuse_rejects_only_out_of_range_indices() { + let mut output = Vec::new(); + upsert_responses_output_item(&mut output, 4095, serde_json::json!({"type": "message"})) + .expect("slot below the safety fuse should be accepted"); + assert_eq!(output.len(), 4096); + + let error = + upsert_responses_output_item(&mut output, 4096, serde_json::json!({"type": "message"})) + .expect_err("slot at the safety fuse should be rejected"); + assert!( + matches!(error, LlmError::Deserialize(message) if message.contains("output_index")) + ); + assert_eq!(output.len(), 4096); + } + #[test] fn llm_error_kind_is_stable_for_adapter_mapping() { assert_eq!(