#![allow(unused_imports, dead_code)]
use super::super::*;
fn write_tool() -> ToolDefinition {
ToolDefinition {
tool_type: "function".into(),
function: FunctionDefinition {
name: "write".into(),
description: None,
parameters: Some(serde_json::json!({
"type": "object",
"properties": {
"filePath": {"type": "string"},
"content": {"type": "string"}
},
"required": ["content", "filePath"]
})),
},
}
}
#[test]
fn salvage_helper_resplits_echoed_key() {
let tools = vec![write_tool()];
let got = salvage_echoed_param(&tools, "write", "parameter", "filePath>\n/tmp/x/session.rs");
assert_eq!(
got,
Some(("filePath".into(), "/tmp/x/session.rs".into())),
"echoed `parameter` key with `filePath>` value prefix must re-split"
);
let got = salvage_echoed_param(&tools, "write", "write", "content>use axum::Json;");
assert_eq!(got, Some(("content".into(), "use axum::Json;".into())));
}
#[test]
fn salvage_helper_never_rewrites_legit_schema_keys() {
let tools = vec![write_tool()];
let got = salvage_echoed_param(&tools, "write", "content", "filePath> is the arg name");
assert_eq!(got, None, "legit schema key must never be salvaged");
let got = salvage_echoed_param(&tools, "write", "bogus", "just some text");
assert_eq!(got, None);
let got = salvage_echoed_param(&tools, "nosuch", "parameter", "filePath>/tmp/a");
assert_eq!(got, None);
}
#[test]
fn buffered_pipeline_recovers_live_shape_1() {
let input = "<tool_call>\n\
<function=write>\n\
<parameter=content>\nuse axum::Json;\n</parameter>\n\
<parameter=parameter>filePath>\n/tmp/x/session.rs\n</parameter>\n\
</function>\n\
</tool_call>";
let (_c, mut calls) = parse_tool_calls(input);
assert_eq!(calls.len(), 1);
let tool = write_tool();
backfill_required_params(&mut calls, std::slice::from_ref(&tool));
let args: serde_json::Value = serde_json::from_str(&calls[0].function.arguments).unwrap();
assert_eq!(
args["filePath"], "/tmp/x/session.rs",
"path must be salvaged from the echoed key"
);
assert_eq!(args["content"], "use axum::Json;");
assert!(
args.get("parameter").is_none(),
"echoed key must be consumed by the salvage, not left behind"
);
}
#[test]
fn buffered_pipeline_recovers_live_shape_2() {
let input = "<tool_call>\n\
<function=write>\n\
<parameter=write>content>use sqlx::SqlitePool;\n</parameter>\n\
<parameter=parameter>filePath>\n/tmp/x/session.rs\n</parameter>\n\
</function>\n\
</tool_call>";
let (_c, mut calls) = parse_tool_calls(input);
assert_eq!(calls.len(), 1);
let tool = write_tool();
backfill_required_params(&mut calls, std::slice::from_ref(&tool));
let args: serde_json::Value = serde_json::from_str(&calls[0].function.arguments).unwrap();
assert_eq!(args["content"], "use sqlx::SqlitePool;");
assert_eq!(args["filePath"], "/tmp/x/session.rs");
assert!(args.get("write").is_none());
assert!(args.get("parameter").is_none());
}
#[test]
fn buffered_salvage_does_not_clobber_populated_target() {
let input = "<tool_call>\n\
<function=write>\n\
<parameter=filePath>\n/tmp/real.rs\n</parameter>\n\
<parameter=content>\nhello\n</parameter>\n\
<parameter=parameter>filePath>\n/tmp/echoed.rs\n</parameter>\n\
</function>\n\
</tool_call>";
let (_c, mut calls) = parse_tool_calls(input);
let tool = write_tool();
backfill_required_params(&mut calls, std::slice::from_ref(&tool));
let args: serde_json::Value = serde_json::from_str(&calls[0].function.arguments).unwrap();
assert_eq!(
args["filePath"], "/tmp/real.rs",
"populated key must win over the echo"
);
}
#[test]
fn streaming_live_fragments_recover_echoed_key() {
let mut det = StreamingToolDetector::new_with_tools(vec![write_tool()]);
let full = "<tool_call>\n<function=write>\n\
<parameter=parameter>filePath>\n/tmp/x/session.rs\n</parameter>\n\
<parameter=content>\nuse axum::Json;\n</parameter>\n\
</function>\n</tool_call>";
let mut outputs = Vec::new();
for chunk in full.as_bytes().chunks(7) {
outputs.extend(det.process(std::str::from_utf8(chunk).unwrap()));
}
outputs.extend(det.flush());
let mut frags = String::new();
for o in &outputs {
if let DetectorOutput::ToolCallArgsFragment { fragment, .. } = o {
frags.push_str(fragment);
}
}
let args: serde_json::Value =
serde_json::from_str(&frags).unwrap_or_else(|e| panic!("bad args json {frags:?}: {e}"));
assert_eq!(args["filePath"], "/tmp/x/session.rs");
assert_eq!(args["content"], "use axum::Json;");
assert!(args.get("parameter").is_none());
}
#[test]
fn buffered_recovers_garbled_close_reopen() {
let input = "<tool_call>\n\
<function=write>\n\
<parameter=content>use axum::Json;\n</parameter<parameter=filePath>\n/tmp/x/+page.svelte\n</parameter>\n\
</function>\n\
</tool_call>";
let (_c, mut calls) = parse_tool_calls(input);
assert_eq!(calls.len(), 1);
let tool = write_tool();
backfill_required_params(&mut calls, std::slice::from_ref(&tool));
let args: serde_json::Value = serde_json::from_str(&calls[0].function.arguments).unwrap();
assert_eq!(
args["content"], "use axum::Json;",
"orphan </parameter tail must be stripped"
);
assert_eq!(args["filePath"], "/tmp/x/+page.svelte");
}
#[test]
fn streaming_recovers_garbled_close_reopen() {
let mut det = StreamingToolDetector::new_with_tools(vec![write_tool()]);
let full = "<tool_call>\n<function=write>\n\
<parameter=content>use axum::Json;\n</parameter<parameter=filePath>\n/tmp/x/+page.svelte\n</parameter>\n\
</function>\n</tool_call>";
let mut outputs = Vec::new();
for chunk in full.as_bytes().chunks(9) {
outputs.extend(det.process(std::str::from_utf8(chunk).unwrap()));
}
outputs.extend(det.flush());
let mut frags = String::new();
for o in &outputs {
if let DetectorOutput::ToolCallArgsFragment { fragment, .. } = o {
frags.push_str(fragment);
}
}
let args: serde_json::Value =
serde_json::from_str(&frags).unwrap_or_else(|e| panic!("bad args json {frags:?}: {e}"));
assert_eq!(
args["filePath"], "/tmp/x/+page.svelte",
"reopened param must be recovered live"
);
assert!(
!args["content"].as_str().unwrap().contains("</parameter"),
"garble must not leak into content"
);
}
#[test]
fn legit_close_prefix_content_not_split() {
let input = "<tool_call>\n\
<function=write>\n\
<parameter=filePath>/tmp/doc.md</parameter>\n\
<parameter=content>the close tag is </parameter followed by text</parameter>\n\
</function>\n\
</tool_call>";
let (_c, mut calls) = parse_tool_calls(input);
let tool = write_tool();
backfill_required_params(&mut calls, std::slice::from_ref(&tool));
let args: serde_json::Value = serde_json::from_str(&calls[0].function.arguments).unwrap();
assert_eq!(
args["content"],
"the close tag is </parameter followed by text"
);
}