gaze_proxy/adapters/
openai.rs1use http::Method;
2use serde_json::Value;
3use url::Url;
4
5use crate::adapter::{
6 push_string, push_text_blocks, walk_all_strings, PiiSurface, ProviderAdapter, SseEvent,
7};
8
9#[derive(Clone, Debug)]
10#[non_exhaustive]
11pub struct OpenAiAdapter {
12 upstream: Url,
13}
14
15impl OpenAiAdapter {
16 pub fn new(upstream: Url) -> Self {
17 Self { upstream }
18 }
19}
20
21#[async_trait::async_trait]
22impl ProviderAdapter for OpenAiAdapter {
23 fn name(&self) -> &'static str {
24 "openai"
25 }
26
27 fn matches_path(&self, method: &Method, path: &str) -> bool {
28 method == Method::POST
29 && matches!(
30 path,
31 "/v1/chat/completions" | "/v1/completions" | "/v1/responses"
32 )
33 }
34
35 fn upstream_base(&self) -> &Url {
36 &self.upstream
37 }
38
39 fn request_pii_surfaces<'a>(&self, body: &'a mut Value) -> Vec<PiiSurface<'a>> {
40 let mut surfaces = Vec::new();
41 if let Value::Object(root) = body {
42 for (key, value) in root {
43 match key.as_str() {
44 "system" => push_string(&mut surfaces, "system", value),
45 "prompt" => push_text_blocks(&mut surfaces, "prompt", value),
46 "instructions" => push_string(&mut surfaces, "instructions", value),
47 "messages" => {
48 if let Value::Array(messages) = value {
49 for (index, message) in messages.iter_mut().enumerate() {
50 collect_message_surfaces(
51 &mut surfaces,
52 format!("messages[{index}]"),
53 message,
54 );
55 }
56 }
57 }
58 "input" => push_text_blocks(&mut surfaces, "input", value),
59 _ => {}
60 }
61 }
62 }
63 surfaces
64 }
65
66 fn response_pii_surfaces<'a>(&self, body: &'a mut Value) -> Vec<PiiSurface<'a>> {
67 let mut surfaces = Vec::new();
68 if let Value::Object(root) = body {
69 for (key, value) in root {
70 match key.as_str() {
71 "choices" => {
72 if let Value::Array(choices) = value {
73 for (index, choice) in choices.iter_mut().enumerate() {
74 if let Value::Object(choice) = choice {
75 for (choice_key, choice_value) in choice {
76 match choice_key.as_str() {
77 "message" => collect_message_surfaces(
78 &mut surfaces,
79 format!("choices[{index}].message"),
80 choice_value,
81 ),
82 "delta" => collect_message_surfaces(
83 &mut surfaces,
84 format!("choices[{index}].delta"),
85 choice_value,
86 ),
87 "text" => push_string(
88 &mut surfaces,
89 format!("choices[{index}].text"),
90 choice_value,
91 ),
92 _ => {}
93 }
94 }
95 }
96 }
97 }
98 }
99 "output" => walk_all_strings(&mut surfaces, "output".to_string(), value),
100 _ => {}
101 }
102 }
103 }
104 surfaces
105 }
106
107 fn sse_event_pii_surfaces<'a>(&self, event: &'a mut SseEvent) -> Vec<PiiSurface<'a>> {
108 self.response_pii_surfaces(&mut event.data)
109 }
110}
111
112fn collect_message_surfaces<'a>(
113 surfaces: &mut Vec<PiiSurface<'a>>,
114 prefix: String,
115 message: &'a mut Value,
116) {
117 let Value::Object(map) = message else {
118 return;
119 };
120 for (key, value) in map {
121 match key.as_str() {
122 "content" => push_text_blocks(surfaces, &format!("{prefix}.content"), value),
123 "tool_results" => push_text_blocks(surfaces, &format!("{prefix}.tool_results"), value),
124 "tool_calls" => {
125 if let Value::Array(tool_calls) = value {
126 for (index, tool_call) in tool_calls.iter_mut().enumerate() {
127 if let Value::Object(tool_call) = tool_call {
128 if let Some(Value::Object(function)) = tool_call.get_mut("function") {
129 if let Some(args) = function.get_mut("arguments") {
130 push_string(
131 surfaces,
132 format!("{prefix}.tool_calls[{index}].function.arguments"),
133 args,
134 );
135 }
136 }
137 }
138 }
139 }
140 }
141 _ => {}
142 }
143 }
144}