use super::{Execution, PathSegment, Template};
use crate::Result;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[expect(
clippy::struct_excessive_bools,
reason = "Expose the six independent capability flags of the native API"
)]
pub struct ChatCapabilities {
pub supports_tools: bool,
pub supports_tool_calls: bool,
pub supports_system_role: bool,
pub supports_parallel_tool_calls: bool,
pub supports_string_content: bool,
pub supports_typed_content: bool,
}
const TOOLS: &str = r#"[{"name":"tool","type":"function","function":{"name":"tool1","description":"Tool description","parameters":{"type":"object","properties":{"arg":{"type":"string","description":"Arg description"}},"required":["arg"]}}}]"#;
const OBJECT_ARGUMENTS: &str = r#"{"arg":"value"}"#;
const STRING_ARGUMENTS: &str = r#""{\"arg\": \"value\"}""#;
fn probe(template: &Template, messages: &str, tools: &str) -> Result<Execution> {
template.execute(&format!(
r#"{{"messages":{messages},"tools":{tools},"bos_token":"","eos_token":"","add_generation_prompt":true}}"#
))
}
fn tool_probe(template: &Template, object_arguments: bool, parallel: bool) -> Result<Execution> {
let arguments = if object_arguments {
OBJECT_ARGUMENTS
} else {
STRING_ARGUMENTS
};
let call = |id: &str| {
format!(
r#"{{"id":"{id}","type":"function","function":{{"name":"tool1","arguments":{arguments}}}}}"#
)
};
let first = call("call00001");
let second = if parallel {
format!(",{}", call("call00002"))
} else {
String::new()
};
probe(
template,
&format!(
r#"[{{"role":"user","content":"User message"}},{{"role":"assistant","content":"","tool_calls":[{first}{second}]}},{{"role":"tool","content":"Tool response","tool_call_id":"call00001"}},{{"role":"assistant","content":"The tool response was 'tool response'"}},{{"role":"user","content":"User message"}}]"#
),
TOOLS,
)
}
fn message_path(index: usize, member: &str) -> Vec<PathSegment> {
vec![
PathSegment::Key("messages".into()),
PathSegment::Index(index),
PathSegment::Key(member.into()),
]
}
fn tool_name_used(execution: &Execution) -> bool {
execution.used(&[
PathSegment::Key("tools".into()),
PathSegment::Index(0),
PathSegment::Key("function".into()),
PathSegment::Key("name".into()),
])
}
pub(super) fn chat_capabilities(template: &Template) -> Result<ChatCapabilities> {
let (supports_string_content, supports_typed_content) = template.content_capabilities()?;
let system = probe(
template,
r#"[{"role":"system","content":"System message"},{"role":"user","content":"User message"}]"#,
"[]",
)?;
let mut result = ChatCapabilities {
supports_tools: true,
supports_tool_calls: true,
supports_system_role: system.used(&message_path(0, "content")),
supports_parallel_tool_calls: false,
supports_string_content,
supports_typed_content,
};
let object = tool_probe(template, true, false)?;
let calls = message_path(1, "tool_calls");
let mut object_arguments = false;
if object.error.is_none() {
result.supports_tools = tool_name_used(&object);
result.supports_tool_calls = object.used(&calls);
if result.supports_tool_calls {
let mut argument = calls.clone();
argument.extend([
PathSegment::Index(0),
PathSegment::Key("function".into()),
PathSegment::Key("arguments".into()),
PathSegment::Key("arg".into()),
]);
object_arguments = object.used(&argument);
}
}
if !object_arguments {
let string = tool_probe(template, false, false)?;
if string.error.is_some() {
result.supports_tools = false;
result.supports_tool_calls = false;
} else {
result.supports_tools &= tool_name_used(&string);
result.supports_tool_calls &= string.used(&calls);
}
}
let parallel = tool_probe(template, object_arguments, true)?;
let mut second = calls;
second.extend([PathSegment::Index(1), PathSegment::Key("function".into())]);
result.supports_parallel_tool_calls = parallel.error.is_none() && parallel.used(&second);
Ok(result)
}
pub(super) struct Observation {
pub(super) rendered: Vec<u8>,
pub(super) success: bool,
pub(super) content_array_access: bool,
}
pub(super) fn detect(mut render: impl FnMut(&str) -> Result<Observation>) -> Result<(bool, bool)> {
const VARIABLES: &str = r#"{"messages":[{"role":"user","content":"STRING_MARKER"}],"tools":[],"bos_token":"","eos_token":"","add_generation_prompt":true}"#;
let observation = render(VARIABLES)?;
let typed = observation.content_array_access;
let marker = observation
.rendered
.array_windows()
.any(|bytes| bytes == b"STRING_MARKER");
let string = observation.success && (!typed || marker);
Ok((string, typed))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn content_probe_distinguishes_iteration_from_rendered_marker() -> Result<()> {
for (rendered, success, array, expected) in [
(b"STRING_MARKER".as_slice(), true, false, (true, false)),
(b"STRING_MARKER".as_slice(), true, true, (true, true)),
(b"".as_slice(), true, true, (false, true)),
(b"".as_slice(), false, false, (false, false)),
] {
assert_eq!(
detect(|variables| {
assert!(variables.contains("STRING_MARKER"));
Ok(Observation {
rendered: rendered.to_vec(),
success,
content_array_access: array,
})
})?,
expected
);
}
Ok(())
}
}