| 966 | } |
| 967 | |
| 968 | fn build_system_prompt(input: &StreamInput) -> Vec<String> { |
| 969 | let mut parts: Vec<String> = Vec::new(); |
| 970 | |
| 971 | // Use agent prompt if available, otherwise fall back to system prompts |
| 972 | if let Some(ref prompt) = input.agent.system_prompt { |
| 973 | parts.push(prompt.clone()); |
| 974 | } |
| 975 | |
| 976 | // Any custom prompts passed into this call |
| 977 | parts.extend(input.system.clone()); |
| 978 | |
| 979 | // Any custom prompt from the last user message |
| 980 | if let MessageInfo::User { |
| 981 | system: Some(ref user_system), |
| 982 | .. |
| 983 | } = input.user |
| 984 | { |
| 985 | parts.push(user_system.clone()); |
| 986 | } |
| 987 | |
| 988 | // Filter empty strings and join into a single system block |
| 989 | let joined: String = parts |
| 990 | .into_iter() |
| 991 | .filter(|s| !s.is_empty()) |
| 992 | .collect::<Vec<_>>() |
| 993 | .join("\n"); |
| 994 | |
| 995 | let mut system = vec![joined]; |
| 996 | |
| 997 | // Rejoin logic: maintain 2-part structure for caching. |
| 998 | // If plugins added extra parts and the header is unchanged, |
| 999 | // collapse everything after the header into a single second part. |
| 1000 | let header = system.first().cloned().unwrap_or_default(); |
| 1001 | if system.len() > 2 && system.first().map(|s| s.as_str()) == Some(header.as_str()) { |
| 1002 | let rest: String = system[1..].join("\n"); |
| 1003 | system = vec![header, rest]; |
| 1004 | } |
| 1005 | |
| 1006 | system |
| 1007 | } |
| 1008 | |
| 1009 | fn hook_payload_object( |
| 1010 | payload: &serde_json::Value, |