@@ -76,7 +76,52 @@ def test_tool_call_output_item_mixed_list() -> None:
7676 assert items [2 ]["type" ] == "input_file" and items [2 ]["file_data" ] == "ZmlsZS1kYXRh"
7777
7878
79+ def test_tool_call_output_item_image_forwards_file_id_and_detail () -> None :
80+ """Ensure image outputs forward provided file_id and detail fields."""
81+ call = _make_tool_call ()
82+ out = ToolOutputImage (file_id = "file_123" , detail = "high" )
83+ payload = ItemHelpers .tool_call_output_item (call , out )
84+
85+ assert payload ["type" ] == "function_call_output"
86+ assert payload ["call_id" ] == call .call_id
87+ item = payload ["output" ][0 ]
88+ assert isinstance (item , dict )
89+ assert item ["type" ] == "input_image"
90+ assert item ["file_id" ] == "file_123"
91+ assert item ["detail" ] == "high"
92+
93+
94+ def test_tool_call_output_item_file_forwards_file_id_and_filename () -> None :
95+ """Ensure file outputs forward provided file_id and filename fields."""
96+ call = _make_tool_call ()
97+ out = ToolOutputFileContent (file_id = "file_456" , filename = "report.pdf" )
98+ payload = ItemHelpers .tool_call_output_item (call , out )
99+
100+ assert payload ["type" ] == "function_call_output"
101+ assert payload ["call_id" ] == call .call_id
102+ item = payload ["output" ][0 ]
103+ assert isinstance (item , dict )
104+ assert item ["type" ] == "input_file"
105+ assert item ["file_id" ] == "file_456"
106+ assert item ["filename" ] == "report.pdf"
107+
108+
109+ def test_tool_call_output_item_file_forwards_file_url () -> None :
110+ """Ensure file outputs forward provided file_url when present."""
111+ call = _make_tool_call ()
112+ out = ToolOutputFileContent (file_url = "https://example.com/report.pdf" )
113+ payload = ItemHelpers .tool_call_output_item (call , out )
114+
115+ assert payload ["type" ] == "function_call_output"
116+ assert payload ["call_id" ] == call .call_id
117+ item = payload ["output" ][0 ]
118+ assert isinstance (item , dict )
119+ assert item ["type" ] == "input_file"
120+ assert item ["file_url" ] == "https://example.com/report.pdf"
121+
122+
79123def test_tool_call_output_item_text_dict_variant () -> None :
124+ """Dict with type='text' and text field should be treated as structured output."""
80125 call = _make_tool_call ()
81126 # Dict variant using the pydantic model schema (type="text").
82127 out = {"type" : "text" , "text" : "hey" }
@@ -91,45 +136,237 @@ def test_tool_call_output_item_text_dict_variant() -> None:
91136 assert item ["text" ] == "hey"
92137
93138
94- def test_tool_call_output_item_image_forwards_file_id_and_detail () -> None :
95- """Ensure image outputs forward provided file_id and detail fields ."""
139+ def test_tool_call_output_item_image_dict_variant () -> None :
140+ """Dict with type=' image' and image_url field should be treated as structured output ."""
96141 call = _make_tool_call ()
97- out = ToolOutputImage ( file_id = "file_123" , detail = "high" )
142+ out = { "type" : "image" , "image_url" : "http://example.com/img.png" , " detail" : "auto" }
98143 payload = ItemHelpers .tool_call_output_item (call , out )
99144
100145 assert payload ["type" ] == "function_call_output"
101146 assert payload ["call_id" ] == call .call_id
147+ assert isinstance (payload ["output" ], list ) and len (payload ["output" ]) == 1
148+ item = payload ["output" ][0 ]
149+ assert isinstance (item , dict )
150+ assert item ["type" ] == "input_image"
151+ assert item ["image_url" ] == "http://example.com/img.png"
152+ assert item ["detail" ] == "auto"
153+
154+
155+ def test_tool_call_output_item_image_dict_variant_with_file_id () -> None :
156+ """Dict with type='image' and image_url field should be treated as structured output."""
157+ call = _make_tool_call ()
158+ out = {"type" : "image" , "file_id" : "file_123" }
159+ payload = ItemHelpers .tool_call_output_item (call , out )
160+
161+ assert payload ["type" ] == "function_call_output"
162+ assert payload ["call_id" ] == call .call_id
163+ assert isinstance (payload ["output" ], list ) and len (payload ["output" ]) == 1
102164 item = payload ["output" ][0 ]
103165 assert isinstance (item , dict )
104166 assert item ["type" ] == "input_image"
105167 assert item ["file_id" ] == "file_123"
106- assert item ["detail" ] == "high"
107168
108169
109- def test_tool_call_output_item_file_forwards_file_id_and_filename () -> None :
110- """Ensure file outputs forward provided file_id and filename fields ."""
170+ def test_tool_call_output_item_file_dict_variant_with_file_data () -> None :
171+ """Dict with type=' file' and file_data field should be treated as structured output ."""
111172 call = _make_tool_call ()
112- out = ToolOutputFileContent ( file_id = "file_456" , filename = " report.pdf")
173+ out = { "type" : "file" , "file_data" : "foobar" , " filename" : " report.pdf"}
113174 payload = ItemHelpers .tool_call_output_item (call , out )
114175
115176 assert payload ["type" ] == "function_call_output"
116177 assert payload ["call_id" ] == call .call_id
178+ assert isinstance (payload ["output" ], list ) and len (payload ["output" ]) == 1
117179 item = payload ["output" ][0 ]
118180 assert isinstance (item , dict )
119181 assert item ["type" ] == "input_file"
120- assert item ["file_id " ] == "file_456 "
182+ assert item ["file_data " ] == "foobar "
121183 assert item ["filename" ] == "report.pdf"
122184
123185
124- def test_tool_call_output_item_file_forwards_file_url () -> None :
125- """Ensure file outputs forward provided file_url when present ."""
186+ def test_tool_call_output_item_file_dict_variant_with_file_url () -> None :
187+ """Dict with type=' file' and file_url field should be treated as structured output ."""
126188 call = _make_tool_call ()
127- out = ToolOutputFileContent ( file_url = " https://example.com/report.pdf")
189+ out = { "type" : "file" , " file_url" : " https://example.com/report.pdf", "filename" : "report.pdf" }
128190 payload = ItemHelpers .tool_call_output_item (call , out )
129191
130192 assert payload ["type" ] == "function_call_output"
131193 assert payload ["call_id" ] == call .call_id
194+ assert isinstance (payload ["output" ], list ) and len (payload ["output" ]) == 1
132195 item = payload ["output" ][0 ]
133196 assert isinstance (item , dict )
134197 assert item ["type" ] == "input_file"
135198 assert item ["file_url" ] == "https://example.com/report.pdf"
199+ assert item ["filename" ] == "report.pdf"
200+
201+
202+ def test_tool_call_output_item_file_dict_variant_with_file_id () -> None :
203+ """Dict with type='file' and file_id field should be treated as structured output."""
204+ call = _make_tool_call ()
205+ out = {"type" : "file" , "file_id" : "file_123" , "filename" : "report.pdf" }
206+ payload = ItemHelpers .tool_call_output_item (call , out )
207+
208+ assert payload ["type" ] == "function_call_output"
209+ assert payload ["call_id" ] == call .call_id
210+ assert isinstance (payload ["output" ], list ) and len (payload ["output" ]) == 1
211+ item = payload ["output" ][0 ]
212+ assert isinstance (item , dict )
213+ assert item ["type" ] == "input_file"
214+ assert item ["file_id" ] == "file_123"
215+ assert item ["filename" ] == "report.pdf"
216+
217+
218+ def test_tool_call_output_item_image_with_extra_fields () -> None :
219+ """Dict with type='image', image_url, and extra fields should still be converted."""
220+ call = _make_tool_call ()
221+ out = {"type" : "image" , "image_url" : "http://example.com/img.png" , "foobar" : 213 }
222+ payload = ItemHelpers .tool_call_output_item (call , out )
223+
224+ assert payload ["type" ] == "function_call_output"
225+ assert payload ["call_id" ] == call .call_id
226+ assert isinstance (payload ["output" ], list ) and len (payload ["output" ]) == 1
227+ item = payload ["output" ][0 ]
228+ assert isinstance (item , dict )
229+ assert item ["type" ] == "input_image"
230+ assert item ["image_url" ] == "http://example.com/img.png"
231+ # Extra field should be ignored by Pydantic
232+ assert "foobar" not in item
233+
234+
235+ def test_tool_call_output_item_mixed_list_with_valid_dicts () -> None :
236+ """List with valid dict variants (with type field) should be converted."""
237+ call = _make_tool_call ()
238+ out = [
239+ {"type" : "text" , "text" : "hello" },
240+ {"type" : "image" , "image_url" : "http://example.com/img.png" },
241+ {"type" : "file" , "file_id" : "file_123" },
242+ ]
243+ payload = ItemHelpers .tool_call_output_item (call , out )
244+
245+ assert payload ["type" ] == "function_call_output"
246+ assert payload ["call_id" ] == call .call_id
247+ assert isinstance (payload ["output" ], list ) and len (payload ["output" ]) == 3
248+
249+ assert payload ["output" ][0 ]["type" ] == "input_text"
250+ assert payload ["output" ][0 ]["text" ] == "hello"
251+ assert payload ["output" ][1 ]["type" ] == "input_image"
252+ assert payload ["output" ][1 ]["image_url" ] == "http://example.com/img.png"
253+ assert payload ["output" ][2 ]["type" ] == "input_file"
254+ assert payload ["output" ][2 ]["file_id" ] == "file_123"
255+
256+
257+ def test_tool_call_output_item_text_type_only_not_converted () -> None :
258+ """Dict with only type='text' should NOT be treated as structured output."""
259+ call = _make_tool_call ()
260+ out = {"type" : "text" }
261+ payload = ItemHelpers .tool_call_output_item (call , out )
262+
263+ assert payload ["type" ] == "function_call_output"
264+ assert payload ["call_id" ] == call .call_id
265+ # Should be converted to string since it doesn't have required fields
266+ assert isinstance (payload ["output" ], str )
267+ assert payload ["output" ] == "{'type': 'text'}"
268+
269+
270+ def test_tool_call_output_item_image_type_only_not_converted () -> None :
271+ """Dict with only type='image' should NOT be treated as structured output."""
272+ call = _make_tool_call ()
273+ out = {"type" : "image" }
274+ payload = ItemHelpers .tool_call_output_item (call , out )
275+
276+ assert payload ["type" ] == "function_call_output"
277+ assert payload ["call_id" ] == call .call_id
278+ # Should be converted to string since it doesn't have required fields
279+ assert isinstance (payload ["output" ], str )
280+ assert payload ["output" ] == "{'type': 'image'}"
281+
282+
283+ def test_tool_call_output_item_file_type_only_not_converted () -> None :
284+ """Dict with only type='file' should NOT be treated as structured output."""
285+ call = _make_tool_call ()
286+ out = {"type" : "file" }
287+ payload = ItemHelpers .tool_call_output_item (call , out )
288+
289+ assert payload ["type" ] == "function_call_output"
290+ assert payload ["call_id" ] == call .call_id
291+ assert isinstance (payload ["output" ], str )
292+ assert payload ["output" ] == "{'type': 'file'}"
293+
294+
295+ def test_tool_call_output_item_empty_dict_not_converted () -> None :
296+ """Empty dict should NOT be treated as structured output."""
297+ call = _make_tool_call ()
298+ out : dict [str , str ] = {}
299+ payload = ItemHelpers .tool_call_output_item (call , out )
300+
301+ assert payload ["type" ] == "function_call_output"
302+ assert payload ["call_id" ] == call .call_id
303+ assert isinstance (payload ["output" ], str )
304+ assert payload ["output" ] == "{}"
305+
306+
307+ def test_tool_call_output_item_dict_without_type_not_converted () -> None :
308+ """Dict without 'type' field should NOT be treated as structured output."""
309+ call = _make_tool_call ()
310+ out = {"msg" : "1234" }
311+ payload = ItemHelpers .tool_call_output_item (call , out )
312+
313+ assert payload ["type" ] == "function_call_output"
314+ assert payload ["call_id" ] == call .call_id
315+ # Should be converted to string since it lacks 'type' field
316+ assert isinstance (payload ["output" ], str )
317+ assert payload ["output" ] == "{'msg': '1234'}"
318+
319+
320+ def test_tool_call_output_item_image_dict_variant_with_location_not_converted () -> None :
321+ """Dict with type='image' and location field should NOT be treated as structured output."""
322+ call = _make_tool_call ()
323+ out = {"type" : "image" , "location" : "/path/to/img.png" }
324+ payload = ItemHelpers .tool_call_output_item (call , out )
325+
326+ assert payload ["type" ] == "function_call_output"
327+ assert payload ["call_id" ] == call .call_id
328+ # Should be converted to string since it lacks required fields (image_url or file_id)
329+ assert isinstance (payload ["output" ], str )
330+ assert payload ["output" ] == "{'type': 'image', 'location': '/path/to/img.png'}"
331+
332+
333+ def test_tool_call_output_item_file_dict_variant_with_path_not_converted () -> None :
334+ """Dict with type='file' and path field should NOT be treated as structured output."""
335+ call = _make_tool_call ()
336+ out = {"type" : "file" , "path" : "/path/to/file.txt" }
337+ payload = ItemHelpers .tool_call_output_item (call , out )
338+
339+ assert payload ["type" ] == "function_call_output"
340+ assert payload ["call_id" ] == call .call_id
341+ # Should be converted to string since it lacks required fields (file_data, file_url, or file_id)
342+ assert isinstance (payload ["output" ], str )
343+ assert payload ["output" ] == "{'type': 'file', 'path': '/path/to/file.txt'}"
344+
345+
346+ def test_tool_call_output_item_list_without_type_not_converted () -> None :
347+ """List with dicts lacking 'type' field should NOT be treated as structured output."""
348+ call = _make_tool_call ()
349+ out = [{"msg" : "foobar" }]
350+ payload = ItemHelpers .tool_call_output_item (call , out )
351+
352+ assert payload ["type" ] == "function_call_output"
353+ assert payload ["call_id" ] == call .call_id
354+ # Should be converted to string since list items lack 'type' field
355+ assert isinstance (payload ["output" ], str )
356+ assert payload ["output" ] == "[{'msg': 'foobar'}]"
357+
358+
359+ def test_tool_call_output_item_mixed_list_partial_invalid_not_converted () -> None :
360+ """List with mix of valid and invalid dicts should NOT be treated as structured output."""
361+ call = _make_tool_call ()
362+ out = [
363+ {"type" : "text" , "text" : "hello" }, # Valid
364+ {"msg" : "foobar" }, # Invalid
365+ ]
366+ payload = ItemHelpers .tool_call_output_item (call , out )
367+
368+ assert payload ["type" ] == "function_call_output"
369+ assert payload ["call_id" ] == call .call_id
370+ # All-or-nothing: if any item is invalid, convert entire list to string
371+ assert isinstance (payload ["output" ], str )
372+ assert payload ["output" ] == "[{'type': 'text', 'text': 'hello'}, {'msg': 'foobar'}]"
0 commit comments