use crate::models::chat;
use crate::models::responses as resp;
use serde_json::{Map, Value};
pub fn to_responses_request(
src: &chat::ChatCompletionRequest,
conversation: Option<String>,
) -> resp::ResponsesRequest {
let messages = map_messages(&src.messages);
let tools = src
.tools
.as_ref()
.map(|ts| ts.iter().map(map_tool).collect::<Vec<_>>());
let response_format = src.response_format.as_ref().map(map_response_format);
resp::ResponsesRequest {
model: src.model.clone(),
messages,
temperature: src.temperature,
top_p: src.top_p,
max_output_tokens: src.max_tokens,
stop: src.stop.clone(),
presence_penalty: src.presence_penalty,
frequency_penalty: src.frequency_penalty,
logit_bias: src.logit_bias.clone(),
user: src.user.clone(),
n: src.n,
tools,
tool_choice: src.tool_choice.clone(),
response_format,
stream: src.stream,
conversation,
}
}
pub async fn to_responses_request_with_mcp(
src: &chat::ChatCompletionRequest,
conversation: Option<String>,
mcp_manager: Option<&crate::mcp_client::McpClientManager>,
) -> resp::ResponsesRequest {
let mut request = to_responses_request(src, conversation);
if let Some(manager) = mcp_manager {
if let Ok(mcp_tools) = manager.list_all_tools().await {
let mcp_tool_definitions: Vec<resp::ResponsesToolDefinition> = mcp_tools
.iter()
.map(|tool| {
resp::ResponsesToolDefinition::Function {
function: resp::ResponsesToolFunction {
name: format!("{}_{}", tool.server_name, tool.name),
description: tool.description.clone(),
parameters: tool.input_schema.clone(),
},
}
})
.collect();
let mut all_tools = request.tools.unwrap_or_default();
all_tools.extend(mcp_tool_definitions);
request.tools = if all_tools.is_empty() {
None
} else {
Some(all_tools)
};
}
}
request
}
fn map_messages(src: &[chat::ChatMessage]) -> Vec<resp::ResponsesMessage> {
src.iter()
.map(|m| resp::ResponsesMessage {
role: role_to_string(&m.role).to_string(),
content: m.content.clone(),
name: m.name.clone(),
tool_call_id: m.tool_call_id.clone(),
})
.collect()
}
fn role_to_string(role: &chat::Role) -> &'static str {
match role {
chat::Role::System => "system",
chat::Role::User => "user",
chat::Role::Assistant => "assistant",
chat::Role::Tool => "tool",
chat::Role::Function => "tool",
}
}
fn map_tool(t: &chat::ToolDefinition) -> resp::ResponsesToolDefinition {
match t {
chat::ToolDefinition::Function { function } => resp::ResponsesToolDefinition::Function {
function: resp::ResponsesToolFunction {
name: function.name.clone(),
description: function.description.clone(),
parameters: function.parameters.clone(),
},
},
}
}
fn map_response_format(rf: &chat::ResponseFormat) -> Value {
let mut obj = Map::<String, Value>::new();
obj.insert("type".to_string(), Value::String(rf.kind.clone()));
for (k, v) in rf.extra.iter() {
if k != "type" {
obj.insert(k.clone(), v.clone());
}
}
Value::Object(obj)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::chat::{
ChatCompletionRequest, ChatMessage, FunctionDef, ResponseFormat, Role, ToolDefinition,
};
use serde_json::json;
use std::collections::HashMap;
#[test]
fn maps_basic_fields() {
let req = ChatCompletionRequest {
model: "gpt-4o-mini".into(),
messages: vec![
ChatMessage {
role: Role::System,
content: json!("You are helpful."),
name: None,
tool_call_id: None,
},
ChatMessage {
role: Role::User,
content: json!("Hello"),
name: None,
tool_call_id: None,
},
],
temperature: Some(0.3),
top_p: Some(0.95),
max_tokens: Some(128),
stop: None,
presence_penalty: Some(0.0),
frequency_penalty: Some(0.0),
logit_bias: None,
user: Some("unit".into()),
n: Some(1),
tools: None,
tool_choice: None,
response_format: None,
stream: Some(false),
};
let out = to_responses_request(&req, Some("conv-xyz".into()));
assert_eq!(out.model, "gpt-4o-mini");
assert_eq!(out.messages.len(), 2);
assert_eq!(out.messages[0].role, "system");
assert_eq!(out.messages[1].role, "user");
assert_eq!(out.max_output_tokens, Some(128));
assert_eq!(out.temperature, Some(0.3));
assert_eq!(out.top_p, Some(0.95));
assert_eq!(out.conversation.as_deref(), Some("conv-xyz"));
assert_eq!(out.stream, Some(false));
}
#[test]
fn maps_tools_and_response_format() {
let mut extra = HashMap::new();
extra.insert(
"schema".into(),
json!({"type":"object","properties":{"x":{"type":"string"}}}),
);
let req = ChatCompletionRequest {
model: "gpt-4o-mini".into(),
messages: vec![ChatMessage {
role: Role::User,
content: json!("Call a tool"),
name: None,
tool_call_id: None,
}],
temperature: None,
top_p: None,
max_tokens: None,
stop: None,
presence_penalty: None,
frequency_penalty: None,
logit_bias: None,
user: None,
n: None,
tools: Some(vec![ToolDefinition::Function {
function: FunctionDef {
name: "lookup".into(),
description: Some("Lookup a value".into()),
parameters: json!({
"type": "object",
"properties": { "key": { "type": "string" } },
"required": ["key"]
}),
},
}]),
tool_choice: Some(json!({"type":"function","function":{"name":"lookup"}})),
response_format: Some(ResponseFormat {
kind: "json_object".into(),
extra,
}),
stream: Some(true),
};
let out = to_responses_request(&req, None);
assert!(out.tools.is_some());
let tools = out.tools.unwrap();
assert_eq!(tools.len(), 1);
#[allow(irrefutable_let_patterns)]
if let resp::ResponsesToolDefinition::Function { function } = &tools[0] {
assert_eq!(function.name, "lookup");
assert!(function.description.as_deref() == Some("Lookup a value"));
} else {
panic!("expected function tool");
}
let rf = out.response_format.expect("response_format missing");
assert_eq!(rf.get("type").and_then(|v| v.as_str()), Some("json_object"));
assert!(rf.get("schema").is_some());
assert_eq!(out.stream, Some(true));
}
#[test]
fn maps_function_role_to_tool() {
let req = ChatCompletionRequest {
model: "gpt-4o-mini".into(),
messages: vec![ChatMessage {
role: Role::Function,
content: json!("result"),
name: Some("fn".into()),
tool_call_id: Some("t1".into()),
}],
temperature: None,
top_p: None,
max_tokens: None,
stop: None,
presence_penalty: None,
frequency_penalty: None,
logit_bias: None,
user: None,
n: None,
tools: None,
tool_choice: None,
response_format: None,
stream: None,
};
let out = to_responses_request(&req, None);
assert_eq!(out.messages.len(), 1);
assert_eq!(out.messages[0].role, "tool");
assert_eq!(out.messages[0].name.as_deref(), Some("fn"));
assert_eq!(out.messages[0].tool_call_id.as_deref(), Some("t1"));
}
}