claudetool: accept more inputs in patch tool
Two goals:
- Fewer failures when the intent is obvious
- Ability to dual-track the description (coming soon)
for less powerful models with a single implementation
diff --git a/claudetool/patch.go b/claudetool/patch.go
index 1f3231c..d0be332 100644
--- a/claudetool/patch.go
+++ b/claudetool/patch.go
@@ -111,6 +111,17 @@
Patches []PatchRequest `json:"patches"`
}
+// PatchInputOne is a simplified version of PatchInput for single patch operations.
+type PatchInputOne struct {
+ Path string `json:"path"`
+ Patches PatchRequest `json:"patches"`
+}
+
+type PatchInputOneString struct {
+ Path string `json:"path"`
+ Patches string `json:"patches"` // contains Patches as a JSON string 🤦
+}
+
// PatchRequest represents a single patch operation.
type PatchRequest struct {
Operation string `json:"operation"`
@@ -118,13 +129,52 @@
NewText string `json:"newText,omitempty"`
}
+// Run implements the patch tool logic.
+func (p *PatchTool) Run(ctx context.Context, m json.RawMessage) llm.ToolOut {
+ input, err := p.patchParse(m)
+ var output llm.ToolOut
+ if err != nil {
+ output = llm.ErrorToolOut(err)
+ } else {
+ output = p.patchRun(ctx, m, &input)
+ }
+ if p.Callback != nil {
+ return p.Callback(input, output)
+ }
+ return output
+}
+
+// patchParse parses the input message into a PatchInput structure.
+// It accepts a few different formats, because empirically,
+// LLMs sometimes generate slightly different JSON structures,
+// and we may as well accept such near misses.
+func (p *PatchTool) patchParse(m json.RawMessage) (PatchInput, error) {
+ var input PatchInput
+ originalErr := json.Unmarshal(m, &input)
+ if originalErr == nil {
+ return input, nil
+ }
+ var inputOne PatchInputOne
+ if err := json.Unmarshal(m, &inputOne); err == nil {
+ return PatchInput{Path: inputOne.Path, Patches: []PatchRequest{inputOne.Patches}}, nil
+ }
+ var inputOneString PatchInputOneString
+ if err := json.Unmarshal(m, &inputOneString); err == nil {
+ var onePatch PatchRequest
+ if err := json.Unmarshal([]byte(inputOneString.Patches), &onePatch); err == nil {
+ return PatchInput{Path: inputOneString.Path, Patches: []PatchRequest{onePatch}}, nil
+ }
+ var patches []PatchRequest
+ if err := json.Unmarshal([]byte(inputOneString.Patches), &patches); err == nil {
+ return PatchInput{Path: inputOneString.Path, Patches: patches}, nil
+ }
+ }
+ return PatchInput{}, fmt.Errorf("failed to unmarshal patch input: %w", originalErr)
+}
+
// patchRun implements the guts of the patch tool.
// It populates input from m.
func (p *PatchTool) patchRun(ctx context.Context, m json.RawMessage, input *PatchInput) llm.ToolOut {
- if err := json.Unmarshal(m, &input); err != nil {
- return llm.ErrorfToolOut("failed to unmarshal user_patch input: %w", err)
- }
-
path := input.Path
if !filepath.IsAbs(input.Path) {
if p.Pwd == "" {