use alloc::{borrow::ToOwned, string::String};
use crate::types::ToolOutput;
pub const TOOL_OUTPUT_QUARANTINE_TAG: &str = "tool_output_quarantine";
pub const TOOL_OUTPUT_QUARANTINE_OPEN: &str = "<tool_output_quarantine>";
pub const TOOL_OUTPUT_QUARANTINE_CLOSE: &str = "</tool_output_quarantine>";
const OPEN_FRAME: &str = "<tool_output_quarantine>\n";
const CLOSE_FRAME: &str = "\n</tool_output_quarantine>";
pub const DELIMITER_RULES: &[(&str, &str)] = &[
("<|im_start|>", "<|im_start|>"),
("<|im_end|>", "<|im_end|>"),
("<|endoftext|>", "<|endoftext|>"),
("<|start_header_id|>", "<|start_header_id|>"),
("<|end_header_id|>", "<|end_header_id|>"),
("<|eot_id|>", "<|eot_id|>"),
("<tool_call>", "<tool_call>"),
("</tool_call>", "</tool_call>"),
("<tool_response>", "<tool_response>"),
("</tool_response>", "</tool_response>"),
("[INST]", "[INST]"),
("[/INST]", "[/INST]"),
("<<SYS>>", "<<SYS>>"),
("<</SYS>>", "<</SYS>>"),
("<start_of_turn>", "<start_of_turn>"),
("<end_of_turn>", "<end_of_turn>"),
("<think>", "<think>"),
("</think>", "</think>"),
("<untrusted_tool_output>", "<untrusted_tool_output>"),
("</untrusted_tool_output>", "</untrusted_tool_output>"),
("<untrusted_content>", "<untrusted_content>"),
("</untrusted_content>", "</untrusted_content>"),
("<tool_output_quarantine>", "<tool_output_quarantine>"),
(
"</tool_output_quarantine>",
"</tool_output_quarantine>",
),
("<|begin▁of▁sentence|>", "<|begin▁of▁sentence|>"),
("<|end▁of▁sentence|>", "<|end▁of▁sentence|>"),
("<|User|>", "<|User|>"),
("<|Assistant|>", "<|Assistant|>"),
("<|tool▁calls▁begin|>", "<|tool▁calls▁begin|>"),
("<|user|>", "<|user|>"),
("<|assistant|>", "<|assistant|>"),
("<|system|>", "<|system|>"),
("<|end|>", "<|end|>"),
("<|START_OF_TURN_TOKEN|>", "<|START_OF_TURN_TOKEN|>"),
("<|END_OF_TURN_TOKEN|>", "<|END_OF_TURN_TOKEN|>"),
("[TOOL_CALLS]", "[TOOL_CALLS]"),
("[AVAILABLE_TOOLS]", "[AVAILABLE_TOOLS]"),
("[/TOOL_CALLS]", "[/TOOL_CALLS]"),
("[/AVAILABLE_TOOLS]", "[/AVAILABLE_TOOLS]"),
("\n\nHuman:", "\n\nHuman:"),
("\n\nAssistant:", "\n\nAssistant:"),
("<role>", "<role>"),
("</role>", "</role>"),
("<|role_end|>", "<|role_end|>"),
("<|channel|>", "<|channel|>"),
("<|message|>", "<|message|>"),
];
const fn is_ncname_continue_byte(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_' || b == b'-' || b == b'.'
}
fn match_tag_prefix(bytes: &[u8], i: usize, tag_bytes: &[u8]) -> Option<usize> {
let mut pos = i + 1;
while pos < bytes.len() && bytes[pos].is_ascii_whitespace() {
pos += 1;
}
if pos < bytes.len() && bytes[pos] == b'/' {
pos += 1;
while pos < bytes.len() && bytes[pos].is_ascii_whitespace() {
pos += 1;
}
}
let after_tag = pos.checked_add(tag_bytes.len())?;
if after_tag <= bytes.len()
&& bytes[pos..after_tag].eq_ignore_ascii_case(tag_bytes)
&& !(after_tag < bytes.len() && is_ncname_continue_byte(bytes[after_tag]))
{
Some(after_tag)
} else {
None
}
}
fn has_quarantine_tag_breakout(s: &str) -> bool {
let bytes = s.as_bytes();
let tag_bytes = TOOL_OUTPUT_QUARANTINE_TAG.as_bytes();
for i in 0..bytes.len() {
if bytes[i] == b'<' && match_tag_prefix(bytes, i, tag_bytes).is_some() {
return true;
}
}
false
}
fn sanitize_quarantine_tag_breakouts(s: &str) -> String {
let bytes = s.as_bytes();
let tag_bytes = TOOL_OUTPUT_QUARANTINE_TAG.as_bytes();
let mut out = String::with_capacity(s.len());
let mut i = 0;
while i < bytes.len() {
if bytes[i] == b'<'
&& let Some(after_tag) = match_tag_prefix(bytes, i, tag_bytes)
{
let mut j = after_tag;
while j < bytes.len() && bytes[j] != b'>' && bytes[j] != b'<' {
j += 1;
}
if j < bytes.len() && bytes[j] == b'>' {
out.push_str("<");
out.push_str(&s[i + 1..j]);
out.push_str(">");
i = j + 1;
continue;
}
out.push_str("<");
i += 1;
continue;
}
let ch = s[i..].chars().next().expect("valid utf-8 character");
out.push(ch);
i += ch.len_utf8();
}
out
}
#[must_use]
pub fn neutralize_delimiters(raw: &str) -> String {
let mut out = raw.to_owned();
for &(token, replacement) in DELIMITER_RULES {
if out.contains(token) {
out = out.replace(token, replacement);
}
}
if has_quarantine_tag_breakout(&out) {
out = sanitize_quarantine_tag_breakouts(&out);
}
out
}
#[must_use]
pub fn is_quarantined_output(raw: &str) -> bool {
let Some(rest) = raw.strip_prefix(OPEN_FRAME) else {
return false;
};
let Some(inner) = rest.strip_suffix(CLOSE_FRAME) else {
return false;
};
if DELIMITER_RULES
.iter()
.any(|&(token, _)| inner.contains(token))
{
return false;
}
!has_quarantine_tag_breakout(inner)
}
#[must_use]
pub fn unquarantine_tool_output(raw: &str) -> Option<&str> {
if !is_quarantined_output(raw) {
return None;
}
raw.strip_prefix(OPEN_FRAME)
.and_then(|rest| rest.strip_suffix(CLOSE_FRAME))
}
#[must_use]
pub fn quarantine_tool_output(raw: &str) -> String {
if is_quarantined_output(raw) {
return raw.to_owned();
}
let sanitized = neutralize_delimiters(raw);
let mut buf = String::with_capacity(OPEN_FRAME.len() + sanitized.len() + CLOSE_FRAME.len());
buf.push_str(OPEN_FRAME);
buf.push_str(&sanitized);
buf.push_str(CLOSE_FRAME);
buf
}
impl ToolOutput {
#[must_use]
pub fn quarantined(mut self) -> Self {
self.content = quarantine_tool_output(&self.content);
self
}
#[must_use]
pub fn is_quarantined(&self) -> bool {
is_quarantined_output(&self.content)
}
}