fix(openai): Cline run_commands는 shell string을 우선한다

This commit is contained in:
toki 2026-06-27 22:53:04 +09:00
parent 4aa0867ca2
commit 87d2929a56
2 changed files with 60 additions and 0 deletions

View file

@ -696,6 +696,60 @@ func TestSynthesizesTemplateToolCallKeepsStructuredCommandArgs(t *testing.T) {
}
}
func TestSynthesizesTemplateToolCallFollowsClineUnionSchemaWithShellStrings(t *testing.T) {
content := "확인합니다.\n\n{{run_commands(commands=[{'command': 'git', 'args': ['-C', '/config/workspace/iop', 'status']}, {'command': 'pwd && ls -la /config/workspace/'}])}}"
tools := []any{map[string]any{
"type": "function",
"function": map[string]any{
"name": "run_commands",
"parameters": map[string]any{
"type": "object",
"properties": map[string]any{
"commands": map[string]any{
"type": "array",
"items": map[string]any{
"anyOf": []any{
map[string]any{
"type": "object",
"properties": map[string]any{
"command": map[string]any{"type": "string"},
"args": map[string]any{
"type": "array",
"items": map[string]any{"type": "string"},
},
},
},
map[string]any{"type": "string"},
},
},
},
},
},
},
}}
_, toolCalls := synthesizeToolCallsFromText(content, tools, "run-test")
if len(toolCalls) != 1 {
t.Fatalf("tool_calls len: got %d", len(toolCalls))
}
call := toolCalls[0].(map[string]any)
fn := call["function"].(map[string]any)
var args map[string][]string
if err := json.Unmarshal([]byte(fn["arguments"].(string)), &args); err != nil {
t.Fatalf("arguments JSON: %v", err)
}
if len(args["commands"]) != 2 {
t.Fatalf("commands len: %+v", args["commands"])
}
if args["commands"][0] != "git -C /config/workspace/iop status" {
t.Fatalf("first command: %+v", args["commands"][0])
}
if args["commands"][1] != "pwd && ls -la /config/workspace/" {
t.Fatalf("second command: %+v", args["commands"][1])
}
}
func TestSynthesizesTemplateToolCallConvertsStructuredShellOperatorsToString(t *testing.T) {
content := "확인합니다.\n\n{{run_commands(commands=[{'command': 'cd', 'args': ['/config/workspace/iop', '&&', 'git', 'status', '&&', 'git', 'diff', '--stat']}, {'command': 'ls', 'args': ['-la', '/config/workspace/iop', '&&', 'pwd']}])}}"
tools := []any{map[string]any{

View file

@ -802,6 +802,9 @@ func normalizeCommandArgument(command any, schema commandArgSchema) (any, bool)
}
if args, ok := stringSliceArgument(m["args"]); ok {
commandText = strings.TrimSpace(commandText)
if schema.allowsString {
return renderShellCommand(commandText, args), true
}
if commandExecutableNeedsShell(commandText) || commandArgsNeedShell(args) {
if schema.allowsString || !schema.allowsObject {
return renderShellCommand(commandText, args), true
@ -816,6 +819,9 @@ func normalizeCommandArgument(command any, schema commandArgSchema) (any, bool)
}
commandText = strings.TrimSpace(commandText)
if commandText != "" {
if schema.allowsString {
return commandText, true
}
if commandStringNeedsShell(commandText) {
return commandText, true
}