switchyard_translation/
request_edit.rs1use serde_json::{Map, Value, json};
7use switchyard_protocol::{ContentBlock, InstructionBlock, LlmRequest, Message, Role, WireFormat};
8
9pub fn append_note(request: &mut LlmRequest, note: &str) {
12 request.messages.push(Message::text(Role::User, note));
14 request.temporary_note_count += 1;
15 request.preservation.requests.retain(|format, body| {
16 let Some(body) = body.as_object_mut() else {
17 return false;
18 };
19 let (key, content_type) = match format.as_str() {
20 "openai_chat" | "anthropic_messages" => ("messages", "text"),
21 "openai_responses" => ("input", "input_text"),
22 _ => return false,
23 };
24 if key == "input"
26 && let Some(Value::String(text)) = body.get_mut(key)
27 {
28 let original = std::mem::take(text);
29 body.insert(key.into(), json!([{"role":"user", "content":original}]));
30 }
31 if key == "input" && body.get(key).is_none_or(Value::is_null) {
32 body.insert(key.into(), Value::Array(Vec::new()));
33 }
34 let Some(messages) = body.get_mut(key).and_then(Value::as_array_mut) else {
35 return false;
36 };
37 if format.as_str() == WireFormat::AnthropicMessages.as_str()
38 && let Some(last) = messages.last_mut()
39 && last.get("role").and_then(Value::as_str) == Some("user")
40 {
41 match last.get_mut("content") {
43 Some(Value::String(text)) => {
44 let original = std::mem::take(text);
45 last["content"] = json!([
46 {"type":"text", "text":original},
47 {"type":"text", "text":note}
48 ]);
49 }
50 Some(Value::Array(blocks)) => blocks.push(json!({"type":"text", "text":note})),
51 _ => return false,
52 }
53 } else {
54 messages.push(json!({"role":"user", "content":[{"type":content_type,"text":note}]}));
55 }
56 true
57 });
58}
59
60pub fn prepend_system_prompt(request: &mut LlmRequest, prompt: &str) {
63 request.instructions.insert(
64 0,
65 InstructionBlock {
66 role: Role::System,
67 content: vec![ContentBlock::Text {
68 text: prompt.to_string(),
69 }],
70 },
71 );
72 request.preservation.requests.retain(|format, body| {
73 let Some(body) = body.as_object_mut() else {
74 return false;
75 };
76 match format.as_str() {
77 "openai_chat" => prepend_openai_chat_system(body, prompt),
78 "openai_responses" => prepend_text_field(body, "instructions", prompt),
79 "anthropic_messages" => {
80 body.remove("switchyard_anthropic_request");
81 prepend_anthropic_system(body, prompt)
82 }
83 _ => false,
84 }
85 });
86}
87
88fn prepend_openai_chat_system(body: &mut Map<String, Value>, prompt: &str) -> bool {
90 let message = json!({"role": "system", "content": prompt});
91 match body.get_mut("messages") {
92 None | Some(Value::Null) => {
93 body.insert("messages".to_string(), Value::Array(vec![message]));
94 }
95 Some(Value::Array(messages)) => messages.insert(0, message),
96 Some(_) => return false,
97 }
98 true
99}
100
101fn prepend_text_field(body: &mut Map<String, Value>, key: &str, prompt: &str) -> bool {
103 match body.get_mut(key) {
104 None | Some(Value::Null) => {
105 body.insert(key.to_string(), Value::String(prompt.to_string()));
106 }
107 Some(Value::String(text)) if text.is_empty() => {
108 *text = prompt.to_string();
109 }
110 Some(Value::String(text)) => text.insert_str(0, &format!("{prompt}\n\n")),
111 Some(_) => return false,
112 }
113 true
114}
115
116fn prepend_anthropic_system(body: &mut Map<String, Value>, prompt: &str) -> bool {
118 if let Some(Value::Array(blocks)) = body.get_mut("system") {
119 blocks.insert(0, json!({"type":"text","text":prompt}));
120 true
121 } else {
122 prepend_text_field(body, "system", prompt)
123 }
124}