diff --git a/src/functions/push.rs b/src/functions/push.rs index ebc1f28..9515442 100644 --- a/src/functions/push.rs +++ b/src/functions/push.rs @@ -47,6 +47,9 @@ const RUNNER_COMMON_SOURCE: &str = include_str!("../../scripts/runner-common.ts" const PYTHON_RUNNER_COMMON_SOURCE: &str = include_str!("../../scripts/python_runner_common.py"); const PYTHON_BASELINE_DEPS: &[&str] = &["pydantic", "braintrust", "autoevals", "requests", "openai"]; +const PARAMETER_FUNCTION_TYPE: &str = "parameters"; +const PARAMETER_SCHEMA_MERGE_PATH: [&str; 2] = ["function_data", "__schema"]; +const PARAMETER_METADATA_MERGE_PATH: [&str; 1] = ["metadata"]; // Compatibility shim for existing test harnesses and eval workflows that set // Python interpreter via BT_EVAL_* variables. Preferred path is still // --runner / BT_FUNCTIONS_PUSH_RUNNER. @@ -725,6 +728,18 @@ fn build_code_function_data( }) } +fn apply_parameter_function_merge_fields(object: &mut Map) { + if object.get("function_type").and_then(Value::as_str) != Some(PARAMETER_FUNCTION_TYPE) { + return; + } + + object.insert("_is_merge".to_string(), Value::Bool(true)); + object.insert( + "_merge_paths".to_string(), + json!([PARAMETER_SCHEMA_MERGE_PATH, PARAMETER_METADATA_MERGE_PATH]), + ); +} + #[allow(clippy::too_many_arguments)] async fn push_file( auth_ctx: &super::AuthContext, @@ -846,6 +861,7 @@ async fn push_file( Value::String(function_type.clone()), ); } + apply_parameter_function_merge_fields(&mut obj); if let Some(metadata) = &code.metadata { obj.insert("metadata".to_string(), metadata.clone()); } @@ -923,6 +939,7 @@ async fn push_file( Value::String(args.if_exists.as_str().to_string()), ); } + apply_parameter_function_merge_fields(object); } function_events.push(event); @@ -3144,6 +3161,44 @@ mod tests { assert_eq!(calculate_upload_counts(3, None), (3, 0)); } + #[test] + fn parameter_function_merge_fields_are_applied() { + let mut object = serde_json::json!({ + "function_type": "parameters", + "_is_merge": false, + "_merge_paths": ["old.path"] + }) + .as_object() + .expect("object") + .clone(); + + apply_parameter_function_merge_fields(&mut object); + + assert_eq!(object.get("_is_merge"), Some(&Value::Bool(true))); + assert_eq!( + object.get("_merge_paths"), + Some(&serde_json::json!([ + ["function_data", "__schema"], + ["metadata"] + ])) + ); + } + + #[test] + fn merge_fields_are_not_applied_to_non_parameter_functions() { + let mut object = serde_json::json!({ + "function_type": "tool" + }) + .as_object() + .expect("object") + .clone(); + + apply_parameter_function_merge_fields(&mut object); + + assert!(object.get("_is_merge").is_none()); + assert!(object.get("_merge_paths").is_none()); + } + #[test] fn requirements_reference_escape_is_rejected() { let dir = tempfile::tempdir().expect("tempdir");