use crate::constants::MAX_THINKING_BUFFER_SIZE;
use crate::types::response_api::{OutputItemType, Tool, ToolType};
use super::context::ResponseRequestContext;
use memchr::memmem;
pub fn map_tool_name_to_output_type(
tool_name: &str,
original_tools: Option<&Vec<Tool>>,
) -> OutputItemType {
if let Some(tools) = original_tools {
for t in tools {
if t.name.as_deref() == Some(tool_name) {
return match t.tool_type {
ToolType::WebSearchPreview => OutputItemType::WebSearchCall,
ToolType::FileSearch => OutputItemType::FileSearchCall,
_ => OutputItemType::FunctionCall,
};
}
}
}
match tool_name {
"web_search_preview" | "web_search" => OutputItemType::WebSearchCall,
"file_search" => OutputItemType::FileSearchCall,
_ => OutputItemType::FunctionCall,
}
}
pub fn map_tool_name_to_stream_item_type(
tool_name: &str,
request_context: Option<&ResponseRequestContext>,
) -> String {
let tools = request_context.map(|ctx| &ctx.tools);
match map_tool_name_to_output_type(tool_name, tools) {
OutputItemType::WebSearchCall => "web_search_call".to_string(),
OutputItemType::FileSearchCall => "file_search_call".to_string(),
_ => "function_call".to_string(),
}
}
pub fn extract_queries_from_arguments(arguments: &str) -> Option<Vec<String>> {
if let Ok(value) = serde_json::from_str::<serde_json::Value>(arguments) {
if let Some(query) = value.get("query").and_then(|v| v.as_str()) {
return Some(vec![query.to_string()]);
}
if let Some(queries) = value.get("queries").and_then(|v| v.as_array()) {
let qs: Vec<String> = queries
.iter()
.filter_map(|q| q.as_str().map(|s| s.to_string()))
.collect();
if !qs.is_empty() {
return Some(qs);
}
}
}
None
}
pub fn parse_thought_tags(content: &str) -> (String, Option<String>) {
let mut actual_content = String::new();
let mut reasoning_parts: Vec<String> = Vec::new();
let mut remaining = content;
loop {
let thought_start = remaining.find("<thought>");
let think_start = remaining.find("<think>");
let (start_idx, open_tag, close_tag) = match (thought_start, think_start) {
(Some(t), Some(k)) => {
if t <= k {
(t, "<thought>", "</thought>")
} else {
(k, "<think>", "</think>")
}
}
(Some(t), None) => (t, "<thought>", "</thought>"),
(None, Some(k)) => (k, "<think>", "</think>"),
(None, None) => break,
};
actual_content.push_str(&remaining[..start_idx]);
let after_start = &remaining[start_idx + open_tag.len()..];
if let Some(end_idx) = after_start.find(close_tag) {
let reasoning_content = &after_start[..end_idx];
if !reasoning_content.is_empty() {
reasoning_parts.push(reasoning_content.to_string());
}
remaining = &after_start[end_idx + close_tag.len()..];
} else {
actual_content.push_str(&remaining[start_idx..]);
remaining = "";
break;
}
}
actual_content.push_str(remaining);
let reasoning = if reasoning_parts.is_empty() {
None
} else {
Some(reasoning_parts.join("\n\n"))
};
(actual_content.trim().to_string(), reasoning)
}
pub fn parse_streaming_thinking(
text: &str,
is_thinking: bool,
buffer: &mut String,
) -> (String, Option<String>, bool) {
let mut actual_text = String::new();
let mut reasoning = String::new();
let mut current_is_thinking = is_thinking;
buffer.push_str(text);
if buffer.len() > MAX_THINKING_BUFFER_SIZE {
let flushed = buffer.clone();
buffer.clear();
return (String::new(), Some(flushed), is_thinking);
}
let full_content = buffer.clone();
buffer.clear();
let bytes = full_content.as_bytes();
let mut pos = 0;
while pos < bytes.len() {
if current_is_thinking {
let think_close = memmem::find(&bytes[pos..], b"</think>");
let thought_close = memmem::find(&bytes[pos..], b"</thought>");
match (think_close, thought_close) {
(Some(close_pos), Some(thought_close_pos)) => {
if close_pos <= thought_close_pos {
let content = std::str::from_utf8(&bytes[pos..pos + close_pos]).unwrap_or("");
reasoning.push_str(content);
pos += close_pos + 8;
current_is_thinking = false;
} else {
let content = std::str::from_utf8(&bytes[pos..pos + thought_close_pos]).unwrap_or("");
reasoning.push_str(content);
pos += thought_close_pos + 10;
current_is_thinking = false;
}
}
(Some(close_pos), None) => {
let content = std::str::from_utf8(&bytes[pos..pos + close_pos]).unwrap_or("");
reasoning.push_str(content);
pos += close_pos + 8;
current_is_thinking = false;
}
(None, Some(thought_close_pos)) => {
let content = std::str::from_utf8(&bytes[pos..pos + thought_close_pos]).unwrap_or("");
reasoning.push_str(content);
pos += thought_close_pos + 10;
current_is_thinking = false;
}
(None, None) => {
let remaining = std::str::from_utf8(&bytes[pos..]).unwrap_or("");
buffer.push_str(remaining);
break;
}
}
} else {
let think_open = memmem::find(&bytes[pos..], b"<think>");
let thought_open = memmem::find(&bytes[pos..], b"<thought>");
match (think_open, thought_open) {
(Some(open_pos), Some(thought_open_pos)) => {
if open_pos <= thought_open_pos {
let content = std::str::from_utf8(&bytes[pos..pos + open_pos]).unwrap_or("");
actual_text.push_str(content);
pos += open_pos + 7;
current_is_thinking = true;
} else {
let content = std::str::from_utf8(&bytes[pos..pos + thought_open_pos]).unwrap_or("");
actual_text.push_str(content);
pos += thought_open_pos + 9;
current_is_thinking = true;
}
}
(Some(open_pos), None) => {
let content = std::str::from_utf8(&bytes[pos..pos + open_pos]).unwrap_or("");
actual_text.push_str(content);
pos += open_pos + 7;
current_is_thinking = true;
}
(None, Some(thought_open_pos)) => {
let content = std::str::from_utf8(&bytes[pos..pos + thought_open_pos]).unwrap_or("");
actual_text.push_str(content);
pos += thought_open_pos + 9;
current_is_thinking = true;
}
(None, None) => {
let remaining = std::str::from_utf8(&bytes[pos..]).unwrap_or("");
actual_text.push_str(remaining);
break;
}
}
}
}
let reasoning_delta = if reasoning.is_empty() {
None
} else {
Some(reasoning)
};
(actual_text, reasoning_delta, current_is_thinking)
}
pub fn find_pattern(chars: &[char], start: usize, pattern: &[char]) -> Option<usize> {
if start + pattern.len() > chars.len() {
return None;
}
for i in start..=chars.len() - pattern.len() {
if chars[i..i + pattern.len()] == *pattern {
return Some(i);
}
}
None
}
pub fn sanitize_pseudo_tool_markup(text: &str) -> String {
text.replace("<request_user_input", "<request_user_input")
.replace("</request_user_input>", "</request_user_input>")
.replace("<parameter ", "<parameter ")
.replace("</parameter>", "</parameter>")
}