@@ -38,10 +38,8 @@ const (
3838
3939type responsesInterceptionBase struct {
4040 id uuid.UUID
41- req * ResponsesNewParamsWrapper
42- reqPayload []byte
41+ reqPayload ResponsesRequestPayload
4342 cfg config.OpenAI
44- model string
4543 recorder recorder.Recorder
4644 mcpProxy mcp.ServerProxier
4745 logger slog.Logger
@@ -71,26 +69,37 @@ func (i *responsesInterceptionBase) ID() uuid.UUID {
7169}
7270
7371func (i * responsesInterceptionBase ) Setup (logger slog.Logger , recorder recorder.Recorder , mcpProxy mcp.ServerProxier ) {
74- i .logger = logger .With (slog .F ("model" , i .model ))
72+ i .logger = logger .With (slog .F ("model" , i .Model () ))
7573 i .recorder = recorder
7674 i .mcpProxy = mcpProxy
7775}
7876
7977func (i * responsesInterceptionBase ) Model () string {
80- return i .model
78+ return i .reqPayload . model ()
8179}
8280
8381func (i * responsesInterceptionBase ) CorrelatingToolCallID () * string {
84- if len (i .req .Input .OfInputItemList ) == 0 {
82+ items := gjson .GetBytes (i .reqPayload , "input" )
83+ if ! items .IsArray () {
8584 return nil
8685 }
8786
88- // The tool result should be the last input message.
89- item := i .req .Input .OfInputItemList [len (i .req .Input .OfInputItemList )- 1 ]
90- if item .OfFunctionCallOutput == nil {
87+ arr := items .Array ()
88+ if len (arr ) == 0 {
9189 return nil
9290 }
93- return & item .OfFunctionCallOutput .CallID
91+
92+ last := arr [len (arr )- 1 ]
93+ if last .Get ("type" ).String () != string (constant .ValueOf [constant.FunctionCallOutput ]()) {
94+ return nil
95+ }
96+
97+ callID := last .Get ("call_id" ).String ()
98+ if callID == "" {
99+ return nil
100+ }
101+
102+ return & callID
94103}
95104
96105func (i * responsesInterceptionBase ) baseTraceAttributes (r * http.Request , streaming bool ) []attribute.KeyValue {
@@ -105,13 +114,7 @@ func (i *responsesInterceptionBase) baseTraceAttributes(r *http.Request, streami
105114}
106115
107116func (i * responsesInterceptionBase ) validateRequest (ctx context.Context , w http.ResponseWriter ) error {
108- if i .req == nil {
109- err := errors .New ("developer error: req is nil" )
110- i .sendCustomErr (ctx , w , http .StatusInternalServerError , err )
111- return err
112- }
113-
114- if i .req .Background .Value {
117+ if i .reqPayload .background () {
115118 err := fmt .Errorf ("background requests are currently not supported by AI Bridge" )
116119 i .sendCustomErr (ctx , w , http .StatusNotImplemented , err )
117120 return err
@@ -144,15 +147,15 @@ func (i *responsesInterceptionBase) requestOptions(respCopy *responseCopier) []o
144147 // eg. Codex CLI produces requests without ID set in reasoning items: https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-item-reasoning-id
145148 // when re-encoded, ID field is set to empty string which results
146149 // in bad request while not sending ID field at all somehow works.
147- option .WithRequestBody ("application/json" , i .reqPayload ),
150+ option .WithRequestBody ("application/json" , [] byte ( i .reqPayload ) ),
148151
149152 // copyMiddleware copies body of original response body to the buffer in responseCopier,
150153 // also reference to headers and status code is kept responseCopier.
151154 // responseCopier is used by interceptors to forward response as it was received,
152155 // eliminating any possibility of JSON re-encoding issues.
153156 option .WithMiddleware (respCopy .copyMiddleware ),
154157 }
155- if ! i .req .Stream {
158+ if ! i .reqPayload .Stream () {
156159 opts = append (opts , option .WithRequestTimeout (requestTimeout ))
157160 }
158161 return opts
@@ -161,81 +164,74 @@ func (i *responsesInterceptionBase) requestOptions(respCopy *responseCopier) []o
161164// lastUserPrompt returns input text with "user" role from last input item
162165// or string input value if it is present + bool indicating if input was found or not.
163166// If no such input was found empty string + false is returned.
164- func (i * responsesInterceptionBase ) lastUserPrompt (ctx context. Context ) (string , bool , error ) {
167+ func (i * responsesInterceptionBase ) lastUserPrompt () (string , bool , error ) {
165168 if i == nil {
166169 return "" , false , errors .New ("cannot get last user prompt: nil struct" )
167170 }
168- if i .req == nil {
171+ if i .reqPayload == nil {
169172 return "" , false , errors .New ("cannot get last user prompt: nil request struct" )
170173 }
171174
172- // 'input' field can be a string or array of objects:
173- // https://platform.openai.com/docs/api-reference/responses/create#responses_create-input
174-
175- // Check string variant
176- if i .req .Input .OfString .Valid () {
177- return i .req .Input .OfString .Value , true , nil
175+ inputItems := gjson .GetBytes (i .reqPayload , "input" )
176+ if ! inputItems .Exists () || inputItems .Type == gjson .Null {
177+ return "" , false , nil
178178 }
179179
180- // Fallback to parsing original bytes since golang SDK doesn't properly decode 'Input' field.
181- // If 'type' field of input item is not set it will be omitted from 'Input.OfInputItemList'
182- // It is an optional field according to API: https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-input_message
183- // example: fixtures/openai/responses/blocking/builtin_tool.txtar
184- inputItems := gjson .GetBytes (i .reqPayload , "input" )
180+ if inputItems .Type == gjson .String {
181+ return inputItems .String (), true , nil
182+ }
185183
186184 if ! inputItems .IsArray () {
187- if inputItems .Type == gjson .Null {
188- return "" , false , nil
189- }
190- return "" , false , fmt .Errorf ("unexpected input type: %v" , inputItems .Type .String ())
185+ return "" , false , fmt .Errorf ("unexpected input type: %s" , inputItems .Type )
191186 }
192187
193188 inputItemsArr := inputItems .Array ()
194189 if len (inputItemsArr ) == 0 {
195190 return "" , false , nil
196191 }
197- lastItem := inputItemsArr [len (inputItemsArr )- 1 ]
198192
199- // Request was likely not human-initiated.
193+ lastItem := inputItemsArr [ len ( inputItemsArr ) - 1 ]
200194 if lastItem .Get ("role" ).Str != string (constant .ValueOf [constant.User ]()) {
201195 return "" , false , nil
202196 }
203197
204- // content can be a string or array of objects:
205- // https://platform.openai.com/docs/api-reference/responses/create#responses_create-input-input_item_list-input_message-content
206198 content := lastItem .Get (string (constant .ValueOf [constant.Content ]()))
199+ if ! content .Exists () || content .Type == gjson .Null {
200+ return "" , false , nil
201+ }
202+
203+ if content .Type == gjson .String {
204+ return content .Str , true , nil
205+ }
207206
208- // non array case, should be string
209207 if ! content .IsArray () {
210- if content .Type == gjson .String {
211- return content .Str , true , nil
212- }
213- return "" , false , fmt .Errorf ("unexpected input content type: %v" , content .Type .String ())
208+ return "" , false , fmt .Errorf ("unexpected input content type: %s" , content .Type )
214209 }
215210
216211 var sb strings.Builder
217212 promptExists := false
218213 for _ , c := range content .Array () {
219- // ignore inputs of not `input_text` type
220214 if c .Get (string (constant .ValueOf [constant.Type ]())).Str != string (constant .ValueOf [constant.InputText ]()) {
221215 continue
222216 }
223217
224218 text := c .Get (string (constant .ValueOf [constant.Text ]()))
225- if text .Type == gjson .String {
226- promptExists = true
227- sb .WriteString (text .Str + "\n " )
228- } else {
229- i .logger .Warn (ctx , fmt .Sprintf ("unexpected input content array element text type: %v" , text .Type ))
219+ if text .Type != gjson .String {
220+ continue
221+ }
222+
223+ if promptExists {
224+ sb .WriteByte ('\n' )
230225 }
226+ promptExists = true
227+ sb .WriteString (text .Str )
231228 }
232229
233230 if ! promptExists {
234231 return "" , false , nil
235232 }
236233
237- prompt := strings .TrimSuffix (sb .String (), "\n " )
238- return prompt , true , nil
234+ return sb .String (), true , nil
239235}
240236
241237func (i * responsesInterceptionBase ) recordUserPrompt (ctx context.Context , responseID string , prompt string ) {
0 commit comments