use crate::openai::*;
fn lower_with_tools(
tools: serde_json::Value,
) -> Result<ChatCompletionRequest, LowerResponsesError> {
let req: ResponsesRequest = serde_json::from_value(serde_json::json!({
"model": "test-model",
"input": "ping",
"tools": tools,
}))
.expect("ResponsesRequest deserialize");
lower_responses_to_chat(req, |_| None)
}
#[test]
fn responses_flat_function_tool_accepted() {
let chat = lower_with_tools(serde_json::json!([
{
"type": "function",
"name": "get_weather",
"description": "look up weather",
"parameters": {"type": "object", "properties": {"loc": {"type": "string"}}, "required": ["loc"]}
}
])).expect("flat-form function tool should parse");
let tools = chat.tools.expect("tools present");
assert_eq!(tools.len(), 1);
assert_eq!(tools[0].tool_type, "function");
assert_eq!(tools[0].function.name, "get_weather");
}
#[test]
fn responses_nested_function_tool_still_accepted() {
let chat = lower_with_tools(serde_json::json!([
{
"type": "function",
"function": {
"name": "get_weather",
"parameters": {"type": "object"}
}
}
]))
.expect("nested-form function tool should parse");
let tools = chat.tools.expect("tools present");
assert_eq!(tools.len(), 1);
assert_eq!(tools[0].function.name, "get_weather");
}
#[test]
fn responses_flat_tool_choice_accepted() {
let req: ResponsesRequest = serde_json::from_value(serde_json::json!({
"model": "test",
"input": "ping",
"tool_choice": {"type": "function", "name": "get_weather"},
}))
.unwrap();
let chat = lower_responses_to_chat(req, |_| None).expect("flat tool_choice");
match chat.tool_choice {
Some(crate::tool_parser::ToolChoice::Specific { function }) => {
assert_eq!(function.name, "get_weather");
}
other => panic!("expected Specific tool_choice, got {other:?}"),
}
}
#[test]
fn responses_string_tool_choice_accepted() {
let req: ResponsesRequest = serde_json::from_value(serde_json::json!({
"model": "test",
"input": "ping",
"tool_choice": "required",
}))
.unwrap();
let chat = lower_responses_to_chat(req, |_| None).expect("string tool_choice");
match chat.tool_choice {
Some(crate::tool_parser::ToolChoice::Mode(s)) => {
assert_eq!(s, "required");
}
other => panic!("expected Mode tool_choice, got {other:?}"),
}
}
#[test]
fn responses_input_image_string_url_is_carried() {
let item = serde_json::json!({
"type": "message",
"role": "user",
"content": [
{"type": "input_text", "text": "what is this?"},
{"type": "input_image", "image_url": "data:image/png;base64,AAA"}
]
});
let m = IncomingMessage::from_responses_input_item(&item).expect("message");
assert_eq!(m.content.text, "what is this?");
assert_eq!(
m.content.images().collect::<Vec<_>>(),
vec!["data:image/png;base64,AAA"]
);
}
#[test]
fn responses_input_image_object_url_is_carried() {
let item = serde_json::json!({
"type": "message",
"role": "user",
"content": [
{"type": "input_image", "image_url": {"url": "https://example.com/a.png"}}
]
});
let m = IncomingMessage::from_responses_input_item(&item).expect("message");
assert_eq!(
m.content.images().collect::<Vec<_>>(),
vec!["https://example.com/a.png"]
);
assert_eq!(m.content.text, "");
}
#[test]
fn responses_function_call_output_image_is_carried() {
let item = serde_json::json!({
"type": "function_call_output",
"call_id": "call_1",
"output": [
{"type": "output_text", "text": "screenshot follows"},
{"type": "input_image", "image_url": "data:image/png;base64,BBB"}
]
});
let m = IncomingMessage::from_responses_input_item(&item).expect("tool message");
assert_eq!(m.role, "tool");
assert_eq!(m.tool_call_id.as_deref(), Some("call_1"));
assert_eq!(m.content.text, "screenshot follows");
assert_eq!(
m.content.images().collect::<Vec<_>>(),
vec!["data:image/png;base64,BBB"]
);
}
#[test]
fn responses_function_call_output_string_unchanged() {
let item = serde_json::json!({
"type": "function_call_output",
"call_id": "call_2",
"output": "plain result"
});
let m = IncomingMessage::from_responses_input_item(&item).expect("tool message");
assert_eq!(m.content.text, "plain result");
assert!(!m.content.has_images());
}
#[test]
fn responses_function_call_output_opaque_array_stringified() {
let opaque = serde_json::json!([{"weather": "sunny", "temp_c": 21}]);
let item = serde_json::json!({
"type": "function_call_output",
"call_id": "call_3",
"output": opaque.clone()
});
let m = IncomingMessage::from_responses_input_item(&item).expect("tool message");
assert_eq!(m.content.text, opaque.to_string());
assert!(!m.content.has_images());
}
#[test]
fn responses_in_progress_event_name() {
let ev = ResponsesStreamEvent::InProgress {
sequence_number: 1,
response: ResponsesStreamEnvelope {
id: "resp_test".into(),
object: "response",
created_at: 0,
model: "m".into(),
status: "in_progress",
metadata: None,
},
};
assert_eq!(responses_event_name(&ev), "response.in_progress");
}
#[test]
fn reasoning_item_wire_shape() {
let item = ResponsesOutputItem::Reasoning {
id: "rs_1".into(),
summary: vec![ResponsesSummaryPart::SummaryText {
text: "thought".into(),
}],
};
let json = serde_json::to_string(&item).unwrap();
assert!(json.contains("\"type\":\"reasoning\""), "{json}");
assert!(
json.contains("\"summary\":[{\"type\":\"summary_text\",\"text\":\"thought\"}]"),
"{json}"
);
}
#[test]
fn reasoning_summary_stream_events_wire_shape() {
let delta = ResponsesStreamEvent::ReasoningSummaryTextDelta {
sequence_number: 3,
item_id: "rs_1".into(),
output_index: 0,
summary_index: 0,
delta: "thinking".into(),
};
assert_eq!(
responses_event_name(&delta),
"response.reasoning_summary_text.delta"
);
let json = serde_json::to_string(&delta).unwrap();
assert!(
json.contains("\"type\":\"response.reasoning_summary_text.delta\""),
"{json}"
);
assert!(json.contains("\"summary_index\":0"), "{json}");
assert!(json.contains("\"delta\":\"thinking\""), "{json}");
assert!(!json.contains("reasoning_content"), "{json}");
let added = ResponsesStreamEvent::ReasoningSummaryPartAdded {
sequence_number: 2,
item_id: "rs_1".into(),
output_index: 0,
summary_index: 0,
part: ResponsesSummaryPart::SummaryText {
text: String::new(),
},
};
assert_eq!(
responses_event_name(&added),
"response.reasoning_summary_part.added"
);
let json = serde_json::to_string(&added).unwrap();
assert!(
json.contains("\"part\":{\"type\":\"summary_text\",\"text\":\"\"}"),
"{json}"
);
let text_done = ResponsesStreamEvent::ReasoningSummaryTextDone {
sequence_number: 4,
item_id: "rs_1".into(),
output_index: 0,
summary_index: 0,
text: "thinking".into(),
};
assert_eq!(
responses_event_name(&text_done),
"response.reasoning_summary_text.done"
);
let part_done = ResponsesStreamEvent::ReasoningSummaryPartDone {
sequence_number: 5,
item_id: "rs_1".into(),
output_index: 0,
summary_index: 0,
part: ResponsesSummaryPart::SummaryText {
text: "thinking".into(),
},
};
assert_eq!(
responses_event_name(&part_done),
"response.reasoning_summary_part.done"
);
}