Fix quoted Bash tool titles

This commit is contained in:
iris committed 2026-09-10 00:46:09 -04:00
1 parent 4c15150338
commit f9c8f640ce
3 files changed
+67 -52

No files matched your search

@@ -80,7 +80,11 @@ fun parseToolInput(tool: String, input: String): ToolInput {
) )
} }
val (subjectKey, language) = SUBJECTS[tool] ?: (null to null) val (subjectKey, language) = SUBJECTS[tool] ?: (null to null)
val subject = subjectKey?.let { json.optString(it) }?.takeIf { it.isNotBlank() } val subject =
subjectKey
?.let { json.optString(it) }
?.takeIf { it.isNotBlank() }
?.let { if (tool == "Bash") renderedBashScript(it) ?: it else it }
val description = DESCRIPTIONS.firstNotNullOfOrNull { val description = DESCRIPTIONS.firstNotNullOfOrNull {
json.optString(it).takeIf { v -> v.isNotBlank() } json.optString(it).takeIf { v -> v.isNotBlank() }
} }
@@ -98,6 +102,28 @@ fun parseToolInput(tool: String, input: String): ToolInput {
return ToolInput(subject, language, description, timeout, rest) return ToolInput(subject, language, description, timeout, rest)
} }
/**
* Removes Codex's rendered Bash argv from old transcript rows.
*
* New events arrive normalized by the server, but persisted transcripts keep the input originally
* written to them. Only the outer pair are presentation quoting: quotes inside the command belong
* to the command and must not be parsed as an early end delimiter.
*/
internal fun renderedBashScript(command: String): String? {
val prefix =
listOf("/usr/bin/bash -lc ", "/bin/bash -lc ", "bash -lc ").firstOrNull {
command.startsWith(it)
} ?: return null
val quoted = command.removePrefix(prefix)
return quoted
.takeIf {
it.length >= 2 &&
((it.startsWith('\'') && it.endsWith('\'')) ||
(it.startsWith('"') && it.endsWith('"')))
}
?.substring(1, quoted.lastIndex)
}
/** /**
* A tool call's input: its subject highlighted, then whatever else it carried. * A tool call's input: its subject highlighted, then whatever else it carried.
* *
@@ -0,0 +1,29 @@
package com.example.aiapp
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
class ToolInputTest {
@Test
fun `bash wrapper ignores double quotes inside its outer pair`() {
assertEquals(
"rg -n \"needle\" server app",
renderedBashScript("/usr/bin/bash -lc \"rg -n \"needle\" server app\""),
)
}
@Test
fun `bash wrapper ignores single quotes inside its outer pair`() {
assertEquals(
"printf 'hello'",
renderedBashScript("/bin/bash -lc 'printf 'hello''"),
)
}
@Test
fun `unquoted or unfamiliar commands stay intact`() {
assertNull(renderedBashScript("/usr/bin/bash -lc echo hello"))
assertNull(renderedBashScript("/usr/bin/fish -lc 'echo hello'"))
}
}
+11 -51
View File
@@ -292,59 +292,19 @@ fn command_tool(item: &Value) -> (String, Value) {
} }
/// App-server renders the executor argv into one shell-quoted string. Unwrap only an exact Bash /// App-server renders the executor argv into one shell-quoted string. Unwrap only an exact Bash
/// invocation containing one shell word; extra arguments stay visible rather than being decoded /// invocation whose script has matching outer quotes; extra arguments stay visible rather than
/// into a command that was not actually run. /// being mistaken for part of the script.
fn rendered_bash_script(command: &str) -> Option<String> { fn rendered_bash_script(command: &str) -> Option<String> {
let quoted = ["/usr/bin/bash -lc ", "/bin/bash -lc ", "bash -lc "] let quoted = ["/usr/bin/bash -lc ", "/bin/bash -lc ", "bash -lc "]
.iter() .iter()
.find_map(|prefix| command.strip_prefix(prefix))?; .find_map(|prefix| command.strip_prefix(prefix))?;
if quoted.starts_with('"') { if quoted.len() >= 2
return double_quoted_shell_word(quoted); && ((quoted.starts_with('\'') && quoted.ends_with('\''))
|| (quoted.starts_with('"') && quoted.ends_with('"')))
{
return Some(quoted[1..quoted.len() - 1].to_string());
} }
if !quoted.starts_with('\'') { (!quoted.is_empty() && !quoted.chars().any(char::is_whitespace)).then(|| quoted.to_string())
return (!quoted.is_empty() && !quoted.chars().any(char::is_whitespace))
.then(|| quoted.to_string());
}
let mut rest = quoted;
let mut script = String::new();
loop {
rest = rest.strip_prefix('\'')?;
let end = rest.find('\'')?;
script.push_str(&rest[..end]);
rest = &rest[end + 1..];
if rest.is_empty() {
return Some(script);
}
if let Some(after_quote) = rest.strip_prefix("\\'") {
script.push('\'');
rest = after_quote;
} else {
let after_quote = rest.strip_prefix("\"'\"")?;
script.push('\'');
rest = after_quote;
}
}
}
fn double_quoted_shell_word(quoted: &str) -> Option<String> {
let mut chars = quoted.strip_prefix('"')?.chars();
let mut script = String::new();
while let Some(character) = chars.next() {
match character {
'"' => return chars.next().is_none().then_some(script),
'\\' => match chars.next()? {
escaped @ ('$' | '`' | '"' | '\\') => script.push(escaped),
'\n' => {}
escaped => {
script.push('\\');
script.push(escaped);
}
},
character => script.push(character),
}
}
None
} }
fn shell_word(word: &Value) -> String { fn shell_word(word: &Value) -> String {
@@ -599,21 +559,21 @@ mod tests {
let rendered = tool(&json!({ let rendered = tool(&json!({
"id": "rendered", "id": "rendered",
"type": "commandExecution", "type": "commandExecution",
"command": r#"/usr/bin/bash -lc 'printf '\''%s\n'\'' hello'"# "command": "/usr/bin/bash -lc 'printf \"%s\\n\" hello'"
})); }));
assert_eq!( assert_eq!(
rendered, rendered,
Some(( Some((
"rendered".to_string(), "rendered".to_string(),
"Bash".to_string(), "Bash".to_string(),
json!({"command": "printf '%s\\n' hello"}) json!({"command": "printf \"%s\\n\" hello"})
)) ))
); );
let double_quoted = tool(&json!({ let double_quoted = tool(&json!({
"id": "double-quoted", "id": "double-quoted",
"type": "commandExecution", "type": "commandExecution",
"command": r#"/usr/bin/bash -lc "printf '%s\\n' \"\$HOME\" \\path""# "command": r#"/usr/bin/bash -lc "printf '%s\n' "$HOME" \path""#
})); }));
assert_eq!( assert_eq!(
double_quoted, double_quoted,