Skip to main content

llm_tool_runtime/
provider.rs

1use crate::{ToolBackendKind, ToolDescriptor, ToolError, ToolErrorClass};
2use serde_json::{json, Value};
3
4/// Renders an OpenAI-compatible tool schema from an internal descriptor.
5pub fn render_openai_tool(descriptor: &ToolDescriptor, strict: bool) -> Result<Value, ToolError> {
6    match descriptor.backend_kind {
7        ToolBackendKind::LocalFunction | ToolBackendKind::OpenAiFunction => {
8            let mut schema = descriptor.input_schema.clone();
9
10            // RCL-043: OpenAI strict mode requires additionalProperties: false
11            // and required: [all properties] on every object schema.
12            if strict {
13                if let Some(obj) = schema.as_object_mut() {
14                    obj.insert("additionalProperties".into(), Value::Bool(false));
15
16                    if let Some(props) = obj.get("properties").and_then(|p| p.as_object()) {
17                        let all_keys: Vec<Value> =
18                            props.keys().map(|k| Value::String(k.clone())).collect();
19                        obj.insert("required".into(), Value::Array(all_keys));
20                    }
21                }
22            }
23
24            Ok(json!({
25                "type": "function",
26                "name": descriptor.name,
27                "description": descriptor.description.clone().unwrap_or_default(),
28                "parameters": schema,
29                "strict": strict,
30            }))
31        }
32        ToolBackendKind::OpenAiBuiltIn | ToolBackendKind::RemoteMcp => {
33            descriptor.provider_payload.clone().ok_or_else(|| {
34                ToolError::new(
35                    ToolErrorClass::ProviderContract,
36                    format!(
37                        "tool {} requires provider_payload for OpenAI rendering",
38                        descriptor.name
39                    ),
40                )
41            })
42        }
43        ToolBackendKind::OllamaFunction => Err(ToolError::new(
44            ToolErrorClass::ProviderContract,
45            format!("tool {} is Ollama-only", descriptor.name),
46        )),
47    }
48}
49
50/// Renders an Ollama-compatible tool schema from an internal descriptor.
51pub fn render_ollama_tool(descriptor: &ToolDescriptor) -> Result<Value, ToolError> {
52    match descriptor.backend_kind {
53        ToolBackendKind::LocalFunction | ToolBackendKind::OllamaFunction => Ok(json!({
54            "type": "function",
55            "function": {
56                "name": descriptor.name,
57                "description": descriptor.description.clone().unwrap_or_default(),
58                "parameters": descriptor.input_schema,
59            }
60        })),
61        _ => Err(ToolError::new(
62            ToolErrorClass::ProviderContract,
63            format!("tool {} cannot be rendered for Ollama", descriptor.name),
64        )),
65    }
66}