diff --git a/internal/util/tool_prompt.go b/internal/util/tool_prompt.go
index b0b0f75..9394eda 100644
--- a/internal/util/tool_prompt.go
+++ b/internal/util/tool_prompt.go
@@ -1,5 +1,7 @@
package util
+import "strings"
+
// BuildToolCallInstructions generates the unified tool-calling instruction block
// used by all adapters (OpenAI, Claude, Gemini). It uses attention-optimized
// structure: rules → negative examples → positive examples → anchor.
@@ -19,7 +21,7 @@ func BuildToolCallInstructions(toolNames []string) string {
ex1 = n
used["ex1"] = true
// Write/execute-type tools
- case !used["ex2"] && matchAny(n, "write_to_file", "apply_diff", "execute_command", "Write", "Edit", "MultiEdit", "Bash"):
+ case !used["ex2"] && matchAny(n, "write_to_file", "apply_diff", "execute_command", "exec_command", "Write", "Edit", "MultiEdit", "Bash"):
ex2 = n
used["ex2"] = true
// Interactive/meta tools
@@ -28,6 +30,9 @@ func BuildToolCallInstructions(toolNames []string) string {
used["ex3"] = true
}
}
+ ex1Params := exampleReadParams(ex1)
+ ex2Params := exampleWriteOrExecParams(ex2)
+ ex3Params := exampleInteractiveParams(ex3)
return `TOOL CALL FORMAT — FOLLOW EXACTLY:
@@ -47,6 +52,7 @@ RULES:
4) Do NOT wrap the XML in markdown code fences (no triple backticks).
5) After receiving a tool result, use it directly. Only call another tool if the result is insufficient.
6) If you want to say something AND call a tool, output text first, then the XML block on its own.
+7) Parameters MUST use the exact field names from the selected tool schema.
❌ WRONG — Do NOT do these:
Wrong 1 — mixed text and XML:
@@ -62,7 +68,7 @@ Example A — Single tool:
` + ex1 + `
- {"path":"src/main.go"}
+ ` + ex1Params + `
@@ -70,11 +76,11 @@ Example B — Two tools in parallel:
` + ex1 + `
- {"path":"config.json"}
+ ` + ex1Params + `
` + ex2 + `
- {"path":"output.txt","content":"Hello world"}
+ ` + ex2Params + `
@@ -82,7 +88,7 @@ Example C — Tool with complex nested JSON parameters:
` + ex3 + `
- {"question":"Which approach do you prefer?","follow_up":[{"text":"Option A"},{"text":"Option B"}]}
+ ` + ex3Params + `
@@ -97,3 +103,38 @@ func matchAny(name string, candidates ...string) bool {
}
return false
}
+
+func exampleReadParams(name string) string {
+ switch strings.TrimSpace(name) {
+ case "Read":
+ return `{"file_path":"README.md"}`
+ case "Glob":
+ return `{"pattern":"**/*.go","path":"."}`
+ default:
+ return `{"path":"src/main.go"}`
+ }
+}
+
+func exampleWriteOrExecParams(name string) string {
+ switch strings.TrimSpace(name) {
+ case "Bash", "execute_command":
+ return `{"command":"pwd"}`
+ case "exec_command":
+ return `{"cmd":"pwd"}`
+ case "Edit":
+ return `{"file_path":"README.md","old_string":"foo","new_string":"bar"}`
+ case "MultiEdit":
+ return `{"file_path":"README.md","edits":[{"old_string":"foo","new_string":"bar"}]}`
+ default:
+ return `{"path":"output.txt","content":"Hello world"}`
+ }
+}
+
+func exampleInteractiveParams(name string) string {
+ switch strings.TrimSpace(name) {
+ case "Task":
+ return `{"description":"Investigate flaky tests","prompt":"Run targeted tests and summarize failures"}`
+ default:
+ return `{"question":"Which approach do you prefer?","follow_up":[{"text":"Option A"},{"text":"Option B"}]}`
+ }
+}
diff --git a/internal/util/tool_prompt_test.go b/internal/util/tool_prompt_test.go
new file mode 100644
index 0000000..e10f176
--- /dev/null
+++ b/internal/util/tool_prompt_test.go
@@ -0,0 +1,26 @@
+package util
+
+import (
+ "strings"
+ "testing"
+)
+
+func TestBuildToolCallInstructions_ExecCommandUsesCmdExample(t *testing.T) {
+ out := BuildToolCallInstructions([]string{"exec_command"})
+ if !strings.Contains(out, `exec_command`) {
+ t.Fatalf("expected exec_command in examples, got: %s", out)
+ }
+ if !strings.Contains(out, `{"cmd":"pwd"}`) {
+ t.Fatalf("expected cmd parameter example for exec_command, got: %s", out)
+ }
+}
+
+func TestBuildToolCallInstructions_ExecuteCommandUsesCommandExample(t *testing.T) {
+ out := BuildToolCallInstructions([]string{"execute_command"})
+ if !strings.Contains(out, `execute_command`) {
+ t.Fatalf("expected execute_command in examples, got: %s", out)
+ }
+ if !strings.Contains(out, `{"command":"pwd"}`) {
+ t.Fatalf("expected command parameter example for execute_command, got: %s", out)
+ }
+}