fix: Tool call improvements for Windows systems

This commit is contained in:
2026-04-08 12:49:43 -06:00
parent e98bf56a2b
commit ed59051f3d
8 changed files with 57 additions and 22 deletions
Generated
+1
View File
@@ -3137,6 +3137,7 @@ dependencies = [
"crossterm 0.28.1",
"dirs",
"duct",
"dunce",
"fancy-regex",
"futures-util",
"fuzzy-matcher",
+1
View File
@@ -18,6 +18,7 @@ anyhow = "1.0.69"
bytes = "1.4.0"
clap = { version = "4.5.40", features = ["cargo", "derive", "wrap_help"] }
dirs = "6.0.0"
dunce = "1.0.5"
futures-util = "0.3.29"
inquire = "0.9.4"
is-terminal = "0.4.9"
+7 -1
View File
@@ -50,7 +50,13 @@ def parse_raw_data(data):
def parse_argv():
agent_func = sys.argv[1]
agent_data = sys.argv[2]
tool_data_file = os.environ.get("LLM_TOOL_DATA_FILE")
if tool_data_file and os.path.isfile(tool_data_file):
with open(tool_data_file, "r", encoding="utf-8") as f:
agent_data = f.read()
else:
agent_data = sys.argv[2]
if (not agent_data) or (not agent_func):
print("Usage: ./{agent_name}.py <agent-func> <agent-data>", file=sys.stderr)
+5 -2
View File
@@ -14,7 +14,11 @@ main() {
parse_argv() {
agent_func="$1"
agent_data="$2"
if [[ -n "$LLM_TOOL_DATA_FILE" ]] && [[ -f "$LLM_TOOL_DATA_FILE" ]]; then
agent_data="$(cat "$LLM_TOOL_DATA_FILE")"
else
agent_data="$2"
fi
if [[ -z "$agent_data" ]] || [[ -z "$agent_func" ]]; then
die "usage: ./{agent_name}.sh <agent-func> <agent-data>"
fi
@@ -57,7 +61,6 @@ run() {
if [[ "$OS" == "Windows_NT" ]]; then
set -o igncr
tools_path="$(cygpath -w "$tools_path")"
tool_data="$(echo "$tool_data" | sed 's/\\/\\\\/g')"
fi
jq_script="$(cat <<-'EOF'
+5
View File
@@ -49,6 +49,11 @@ def parse_raw_data(data):
def parse_argv():
tool_data_file = os.environ.get("LLM_TOOL_DATA_FILE")
if tool_data_file and os.path.isfile(tool_data_file):
with open(tool_data_file, "r", encoding="utf-8") as f:
return f.read()
argv = sys.argv[:] + [None] * max(0, 2 - len(sys.argv))
tool_data = argv[1]
+5 -2
View File
@@ -13,7 +13,11 @@ main() {
}
parse_argv() {
tool_data="$1"
if [[ -n "$LLM_TOOL_DATA_FILE" ]] && [[ -f "$LLM_TOOL_DATA_FILE" ]]; then
tool_data="$(cat "$LLM_TOOL_DATA_FILE")"
else
tool_data="$1"
fi
if [[ -z "$tool_data" ]]; then
die "usage: ./{function_name}.sh <tool-data>"
fi
@@ -54,7 +58,6 @@ run() {
if [[ "$OS" == "Windows_NT" ]]; then
set -o igncr
tool_path="$(cygpath -w "$tool_path")"
tool_data="$(echo "$tool_data" | sed 's/\\/\\\\/g')"
fi
jq_script="$(cat <<-'EOF'
+4 -3
View File
@@ -107,6 +107,7 @@ The following variables can be used to change the log level of Loki or the locat
can also pass the `--disable-log-colors` flag as well.
## Miscellaneous Variables
| Environment Variable | Description | Default Value |
|----------------------|--------------------------------------------------------------------------------------------------|---------------|
| `AUTO_CONFIRM` | Bypass all `guard_*` checks in the bash prompt helpers; useful for agent composition and routing | |
| Environment Variable | Description | Default Value |
|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
| `AUTO_CONFIRM` | Bypass all `guard_*` checks in the bash prompt helpers; useful for agent composition and routing | |
| `LLM_TOOL_DATA_FILE` | Set automatically by Loki on Windows. Points to a temporary file containing the JSON tool call data. <br>Tool scripts (`run-tool.sh`, `run-agent.sh`, etc.) read from this file instead of command-line args <br>to avoid JSON escaping issues when data passes through `cmd.exe` → bash. **Not intended to be set by users.** | |
+29 -14
View File
@@ -613,6 +613,7 @@ impl Functions {
)
})?;
let content_template = unsafe { std::str::from_utf8_unchecked(&embedded_file.data) };
let to_script_path = |p: &str| -> String { p.replace('\\', "/") };
let content = match binary_type {
BinaryType::Tool(None) => {
let root_dir = Config::functions_dir();
@@ -622,8 +623,8 @@ impl Functions {
);
content_template
.replace("{function_name}", binary_name)
.replace("{root_dir}", &root_dir.to_string_lossy())
.replace("{tool_path}", &tool_path)
.replace("{root_dir}", &to_script_path(&root_dir.to_string_lossy()))
.replace("{tool_path}", &to_script_path(&tool_path))
}
BinaryType::Tool(Some(agent_name)) => {
let root_dir = Config::agent_data_dir(agent_name);
@@ -633,16 +634,19 @@ impl Functions {
);
content_template
.replace("{function_name}", binary_name)
.replace("{root_dir}", &root_dir.to_string_lossy())
.replace("{tool_path}", &tool_path)
.replace("{root_dir}", &to_script_path(&root_dir.to_string_lossy()))
.replace("{tool_path}", &to_script_path(&tool_path))
}
BinaryType::Agent => content_template
.replace("{agent_name}", binary_name)
.replace("{config_dir}", &Config::config_dir().to_string_lossy()),
.replace(
"{config_dir}",
&to_script_path(&Config::config_dir().to_string_lossy()),
),
}
.replace(
"{prompt_utils_file}",
&Config::bash_prompt_utils_file().to_string_lossy(),
&to_script_path(&Config::bash_prompt_utils_file().to_string_lossy()),
);
if binary_script_file.exists() {
fs::remove_file(&binary_script_file)?;
@@ -666,7 +670,7 @@ impl Functions {
.join(".venv")
.join("Scripts")
.join("activate.bat");
let canonicalized_path = fs::canonicalize(&executable_path)?;
let canonicalized_path = dunce::canonicalize(&executable_path)?;
format!(
"call \"{}\" && {}",
canonicalized_path.to_string_lossy(),
@@ -677,19 +681,16 @@ impl Functions {
let executable_path = which::which("python")
.or_else(|_| which::which("python3"))
.map_err(|_| anyhow!("Python executable not found in PATH"))?;
let canonicalized_path = fs::canonicalize(&executable_path)?;
let canonicalized_path = dunce::canonicalize(&executable_path)?;
canonicalized_path.to_string_lossy().into_owned()
}
_ => bail!("Unsupported language: {}", language.as_ref()),
};
let bin_dir = binary_file
.parent()
.expect("Failed to get parent directory of binary file")
.canonicalize()?
.to_string_lossy()
.into_owned();
let wrapper_binary = binary_script_file
.canonicalize()?
.expect("Failed to get parent directory of binary file");
let bin_dir = dunce::canonicalize(bin_dir)?.to_string_lossy().into_owned();
let wrapper_binary = dunce::canonicalize(&binary_script_file)?
.to_string_lossy()
.into_owned();
let content = formatdoc!(
@@ -1117,6 +1118,20 @@ pub fn run_llm_function(
#[cfg(windows)]
let cmd_name = polyfill_cmd_name(&cmd_name, &bin_dirs);
#[cfg(windows)]
let cmd_args = {
let mut args = cmd_args;
if let Some(json_data) = args.pop() {
let tool_data_file = temp_file("-tool-data-", ".json");
fs::write(&tool_data_file, &json_data)?;
envs.insert(
"LLM_TOOL_DATA_FILE".into(),
tool_data_file.display().to_string(),
);
}
args
};
envs.insert("CLICOLOR_FORCE".into(), "1".into());
envs.insert("FORCE_COLOR".into(), "1".into());