use crate::llm::api::{DeltaSender, LlmResult};
use crate::llm::providers::schema_compat::{
sanitize_schema_for_provider, SchemaCompatProfile, SchemaSurface,
};
use crate::value::{VmError, VmValue};
pub(super) fn vm_err(message: impl Into<String>) -> VmError {
VmError::Thrown(VmValue::string(message.into()))
}
pub(super) fn maybe_emit_delta(delta_tx: Option<DeltaSender>, text: &str) {
if let Some(tx) = delta_tx {
if !text.is_empty() {
let _ = tx.send(text.to_string());
}
}
}
pub(super) fn output_text_block(text: &str) -> serde_json::Value {
serde_json::json!({
"type": "output_text",
"text": text,
"visibility": "public",
})
}
pub(super) fn reasoning_block(text: &str) -> serde_json::Value {
serde_json::json!({
"type": "reasoning",
"text": text,
"visibility": "private",
})
}
pub(super) fn tool_call_block(
id: impl Into<serde_json::Value>,
name: impl Into<serde_json::Value>,
arguments: impl Into<serde_json::Value>,
) -> serde_json::Value {
serde_json::json!({
"type": "tool_call",
"id": id.into(),
"name": name.into(),
"arguments": arguments.into(),
"visibility": "internal",
})
}
pub(super) use harn_llm::wire::request_text_content;
pub(super) fn empty_result(provider: &str, model: &str) -> LlmResult {
LlmResult {
attempts: Default::default(),
text_projection: None,
served_fast: false,
text: String::new(),
tool_calls: Vec::new(),
raw_tool_calls: Vec::new(),
input_tokens: 0,
output_tokens: 0,
cache_read_tokens: 0,
cache_write_tokens: 0,
cache_supported: true,
model: model.to_string(),
provider: provider.to_string(),
thinking: None,
thinking_summary: None,
stop_reason: None,
blocks: Vec::new(),
logprobs: Vec::new(),
telemetry: Box::default(),
}
}
pub(super) fn apply_provider_overrides(
body: &mut serde_json::Value,
overrides: Option<&serde_json::Value>,
) {
crate::llm::provider::apply_provider_wire_overrides(body, overrides);
}
pub(super) fn google_function_declaration_tools(
provider: &str,
model: &str,
tools: Option<&[serde_json::Value]>,
) -> Option<serde_json::Value> {
let declarations: Vec<serde_json::Value> = tools
.unwrap_or_default()
.iter()
.filter_map(|tool| google_function_declaration(provider, model, tool))
.collect();
(!declarations.is_empty())
.then(|| serde_json::json!([{ "functionDeclarations": declarations }]))
}
fn google_function_declaration(
provider: &str,
model: &str,
tool: &serde_json::Value,
) -> Option<serde_json::Value> {
let function = tool.get("function").unwrap_or(tool);
let name = function
.get("name")
.and_then(serde_json::Value::as_str)
.filter(|name| !name.is_empty())?;
let mut declaration = serde_json::json!({ "name": name });
if let Some(description) = function.get("description") {
declaration["description"] = description.clone();
}
if let Some(parameters) = function
.get("parameters")
.or_else(|| function.get("input_schema"))
{
declaration["parameters"] = sanitize_schema_for_provider(
provider,
model,
SchemaCompatProfile::Google,
SchemaSurface::ToolParameters,
parameters,
);
}
Some(declaration)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn provider_overrides_do_not_emit_harn_control_fields() {
let mut body = serde_json::json!({"model": "example"});
let overrides = serde_json::json!({
"force_native_tool_search": true,
"custom_wire_field": "kept",
});
apply_provider_overrides(&mut body, Some(&overrides));
assert!(body.get("force_native_tool_search").is_none());
assert_eq!(body["custom_wire_field"], "kept");
}
}