mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-31 16:49:34 +00:00
Fix schema-guided tool parameter coercion (#1143)
* Fix schema-guided tool parameter coercion * Fix CI checks for coercion regression tests * Finish panic-scan annotations * Avoid redundant worker param preparation * Keep panic-scan annotations rustfmt-stable * Handle nullable WASM schema review feedback * Address param coercion review notes
This commit is contained in:
+59
-4
@@ -8,7 +8,7 @@ use crate::context::JobContext;
|
||||
use crate::error::Error;
|
||||
use crate::llm::ChatMessage;
|
||||
use crate::safety::SafetyLayer;
|
||||
use crate::tools::{ToolRegistry, redact_params};
|
||||
use crate::tools::{ToolRegistry, prepare_tool_params, redact_params};
|
||||
|
||||
/// Execute a tool with safety checks: lookup → validate → timeout → execute → serialize.
|
||||
///
|
||||
@@ -29,8 +29,10 @@ pub async fn execute_tool_with_safety(
|
||||
name: tool_name.to_string(),
|
||||
})?;
|
||||
|
||||
let normalized_params = prepare_tool_params(tool.as_ref(), params);
|
||||
|
||||
// Validate tool parameters
|
||||
let validation = safety.validator().validate_tool_params(params);
|
||||
let validation = safety.validator().validate_tool_params(&normalized_params);
|
||||
if !validation.is_valid {
|
||||
let details = validation
|
||||
.errors
|
||||
@@ -45,7 +47,7 @@ pub async fn execute_tool_with_safety(
|
||||
.into());
|
||||
}
|
||||
|
||||
let safe_params = redact_params(params, tool.sensitive_params());
|
||||
let safe_params = redact_params(&normalized_params, tool.sensitive_params());
|
||||
tracing::debug!(
|
||||
tool = %tool_name,
|
||||
params = %safe_params,
|
||||
@@ -56,7 +58,7 @@ pub async fn execute_tool_with_safety(
|
||||
let timeout = tool.execution_timeout();
|
||||
let start = std::time::Instant::now();
|
||||
let result = tokio::time::timeout(timeout, async {
|
||||
tool.execute(params.clone(), job_ctx).await
|
||||
tool.execute(normalized_params.clone(), job_ctx).await
|
||||
})
|
||||
.await;
|
||||
let elapsed = start.elapsed();
|
||||
@@ -237,6 +239,39 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
struct ArrayEchoTool;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl Tool for ArrayEchoTool {
|
||||
fn name(&self) -> &str {
|
||||
"array_echo"
|
||||
}
|
||||
fn description(&self) -> &str {
|
||||
"Echoes normalized params"
|
||||
}
|
||||
fn parameters_schema(&self) -> serde_json::Value {
|
||||
serde_json::json!({
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"values": {
|
||||
"type": "array",
|
||||
"items": { "type": "integer" }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
async fn execute(
|
||||
&self,
|
||||
params: serde_json::Value,
|
||||
_ctx: &JobContext,
|
||||
) -> Result<ToolOutput, ToolError> {
|
||||
Ok(ToolOutput::success(params, Duration::default()))
|
||||
}
|
||||
fn requires_sanitization(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn test_safety() -> SafetyLayer {
|
||||
SafetyLayer::new(&crate::config::SafetyConfig {
|
||||
max_output_length: 100_000,
|
||||
@@ -348,6 +383,26 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_execute_normalizes_stringified_array_params() {
|
||||
let registry = registry_with(vec![Arc::new(ArrayEchoTool)]).await;
|
||||
let safety = test_safety();
|
||||
|
||||
let result = execute_tool_with_safety(
|
||||
®istry,
|
||||
&safety,
|
||||
"array_echo",
|
||||
&serde_json::json!({"values": "[\"1\", \"2\", 3]"}),
|
||||
&test_job_ctx(),
|
||||
)
|
||||
.await
|
||||
.expect("array_echo should succeed"); // safety: test-only assertion
|
||||
|
||||
let output: serde_json::Value =
|
||||
serde_json::from_str(&result).expect("tool result should be valid JSON"); // safety: test-only assertion
|
||||
assert_eq!(output["values"], serde_json::json!([1, 2, 3])); // safety: test-only assertion
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_tool_result_success() {
|
||||
let safety = test_safety();
|
||||
|
||||
Reference in New Issue
Block a user