diff --git a/internal/toolcall/tool_prompt.go b/internal/toolcall/tool_prompt.go index a327261..1a8ed1e 100644 --- a/internal/toolcall/tool_prompt.go +++ b/internal/toolcall/tool_prompt.go @@ -140,21 +140,21 @@ func firstScriptExample(names []string) (promptToolExample, bool) { func renderToolExampleBlock(calls []promptToolExample) string { var b strings.Builder - b.WriteString("<|DSML|tool_calls>\n") + b.WriteString("<|DSML|tool_calls>\n") for _, call := range calls { - b.WriteString(` <|DSML|invoke name="`) + b.WriteString(` <|DSML|invoke name="`) b.WriteString(call.name) b.WriteString(`">` + "\n") b.WriteString(indentPromptParameters(call.params, " ")) - b.WriteString("\n \n") + b.WriteString("\n \n") } - b.WriteString("") + b.WriteString("") return b.String() } func indentPromptParameters(body, indent string) string { if strings.TrimSpace(body) == "" { - return indent + `<|DSML|parameter name="content">` + return indent + `<|DSML|parameter name="content">` } lines := strings.Split(body, "\n") for i, line := range lines { @@ -168,7 +168,7 @@ func indentPromptParameters(body, indent string) string { } func wrapParameter(name, inner string) string { - return `<|DSML|parameter name="` + name + `">` + inner + `` + return `<|DSML|parameter name="` + name + `">` + inner + `` } func exampleBasicParams(name string) (string, bool) { @@ -194,7 +194,7 @@ func exampleBasicParams(name string) (string, bool) { case "Edit": return wrapParameter("file_path", promptCDATA("README.md")) + "\n" + wrapParameter("old_string", promptCDATA("foo")) + "\n" + wrapParameter("new_string", promptCDATA("bar")), true case "MultiEdit": - return wrapParameter("file_path", promptCDATA("README.md")) + "\n" + `<|DSML|parameter name="edits">` + promptCDATA("foo") + `` + promptCDATA("bar") + ``, true + return wrapParameter("file_path", promptCDATA("README.md")) + "\n" + `<|DSML|parameter name="edits">` + promptCDATA("foo") + `` + promptCDATA("bar") + ``, true } return "", false } @@ -202,11 +202,11 @@ func exampleBasicParams(name string) (string, bool) { func exampleNestedParams(name string) (string, bool) { switch strings.TrimSpace(name) { case "MultiEdit": - return wrapParameter("file_path", promptCDATA("README.md")) + "\n" + `<|DSML|parameter name="edits">` + promptCDATA("foo") + `` + promptCDATA("bar") + ``, true + return wrapParameter("file_path", promptCDATA("README.md")) + "\n" + `<|DSML|parameter name="edits">` + promptCDATA("foo") + `` + promptCDATA("bar") + ``, true case "Task": return wrapParameter("description", promptCDATA("Investigate flaky tests")) + "\n" + wrapParameter("prompt", promptCDATA("Run targeted tests and summarize failures")), true case "ask_followup_question": - return wrapParameter("question", promptCDATA("Which approach do you prefer?")) + "\n" + `<|DSML|parameter name="follow_up">` + promptCDATA("Option A") + `` + promptCDATA("Option B") + ``, true + return wrapParameter("question", promptCDATA("Which approach do you prefer?")) + "\n" + `<|DSML|parameter name="follow_up">` + promptCDATA("Option A") + `` + promptCDATA("Option B") + ``, true } return "", false } diff --git a/internal/toolcall/tool_prompt_test.go b/internal/toolcall/tool_prompt_test.go index 482b8bc..5e5eca6 100644 --- a/internal/toolcall/tool_prompt_test.go +++ b/internal/toolcall/tool_prompt_test.go @@ -7,20 +7,20 @@ import ( func TestBuildToolCallInstructions_ExecCommandUsesCmdExample(t *testing.T) { out := BuildToolCallInstructions([]string{"exec_command"}) - if !strings.Contains(out, `<|DSML|invoke name="exec_command">`) { + if !strings.Contains(out, `<|DSML|invoke name="exec_command">`) { t.Fatalf("expected exec_command in examples, got: %s", out) } - if !strings.Contains(out, `<|DSML|parameter name="cmd">`) { + if !strings.Contains(out, `<|DSML|parameter name="cmd">`) { 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, `<|DSML|invoke name="execute_command">`) { + if !strings.Contains(out, `<|DSML|invoke name="execute_command">`) { t.Fatalf("expected execute_command in examples, got: %s", out) } - if !strings.Contains(out, `<|DSML|parameter name="command">`) { + if !strings.Contains(out, `<|DSML|parameter name="command">`) { t.Fatalf("expected command parameter example for execute_command, got: %s", out) } } @@ -34,20 +34,20 @@ func TestBuildToolCallInstructions_BashUsesCommandAndDescriptionExamples(t *test sawDescription := false for _, block := range blocks { - if !strings.Contains(block, `<|DSML|parameter name="command">`) { + if !strings.Contains(block, `<|DSML|parameter name="command">`) { t.Fatalf("expected every Bash example to use command parameter, got: %s", block) } - if strings.Contains(block, `<|DSML|parameter name="path">`) || strings.Contains(block, `<|DSML|parameter name="content">`) { + if strings.Contains(block, `<|DSML|parameter name="path">`) || strings.Contains(block, `<|DSML|parameter name="content">`) { t.Fatalf("expected Bash examples not to use file write parameters, got: %s", block) } - if strings.Contains(block, `<|DSML|parameter name="description">`) { + if strings.Contains(block, `<|DSML|parameter name="description">`) { sawDescription = true } } if !sawDescription { t.Fatalf("expected Bash long-script example to include description, got: %s", out) } - if strings.Contains(out, `<|DSML|invoke name="Read">`) { + if strings.Contains(out, `<|DSML|invoke name="Read">`) { t.Fatalf("expected examples to avoid unavailable hard-coded Read tool, got: %s", out) } } @@ -60,10 +60,10 @@ func TestBuildToolCallInstructions_ExecuteCommandLongScriptUsesCommand(t *testin } for _, block := range blocks { - if !strings.Contains(block, `<|DSML|parameter name="command">`) { + if !strings.Contains(block, `<|DSML|parameter name="command">`) { t.Fatalf("expected execute_command examples to use command parameter, got: %s", block) } - if strings.Contains(block, `<|DSML|parameter name="path">`) || strings.Contains(block, `<|DSML|parameter name="content">`) { + if strings.Contains(block, `<|DSML|parameter name="path">`) || strings.Contains(block, `<|DSML|parameter name="content">`) { t.Fatalf("expected execute_command examples not to use file write parameters, got: %s", block) } } @@ -80,10 +80,10 @@ func TestBuildToolCallInstructions_ExecCommandLongScriptUsesCmd(t *testing.T) { } for _, block := range blocks { - if !strings.Contains(block, `<|DSML|parameter name="cmd">`) { + if !strings.Contains(block, `<|DSML|parameter name="cmd">`) { t.Fatalf("expected exec_command examples to use cmd parameter, got: %s", block) } - if strings.Contains(block, `<|DSML|parameter name="command">`) || strings.Contains(block, `<|DSML|parameter name="path">`) || strings.Contains(block, `<|DSML|parameter name="content">`) { + if strings.Contains(block, `<|DSML|parameter name="command">`) || strings.Contains(block, `<|DSML|parameter name="path">`) || strings.Contains(block, `<|DSML|parameter name="content">`) { t.Fatalf("expected exec_command examples not to use command or file write parameters, got: %s", block) } } @@ -100,10 +100,10 @@ func TestBuildToolCallInstructions_WriteUsesFilePathAndContent(t *testing.T) { } for _, block := range blocks { - if !strings.Contains(block, `<|DSML|parameter name="file_path">`) || !strings.Contains(block, `<|DSML|parameter name="content">`) { + if !strings.Contains(block, `<|DSML|parameter name="file_path">`) || !strings.Contains(block, `<|DSML|parameter name="content">`) { t.Fatalf("expected Write examples to use file_path and content, got: %s", block) } - if strings.Contains(block, `<|DSML|parameter name="path">`) { + if strings.Contains(block, `<|DSML|parameter name="path">`) { t.Fatalf("expected Write examples not to use path, got: %s", block) } } @@ -120,7 +120,7 @@ func TestBuildToolCallInstructions_AnchorsMissingOpeningWrapperFailureMode(t *te } func findInvokeBlocks(text, name string) []string { - open := `<|DSML|invoke name="` + name + `">` + open := `<|DSML|invoke name="` + name + `">` remaining := text blocks := []string{} for { @@ -129,11 +129,11 @@ func findInvokeBlocks(text, name string) []string { return blocks } remaining = remaining[start:] - end := strings.Index(remaining, ``) + end := strings.Index(remaining, ``) if end < 0 { return blocks } - end += len(``) + end += len(``) blocks = append(blocks, remaining[:end]) remaining = remaining[end:] }