Skip to content

Commit 994e984

Browse files
committed
claudetool: add simplified patch support
For weaker models. Also, improve fallback parsing introduced earlier.
1 parent 7f18fb6 commit 994e984

File tree

6 files changed

+91
-19
lines changed

6 files changed

+91
-19
lines changed

claudetool/patch.go

Lines changed: 54 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,11 @@ type PatchTool struct {
3131
Callback PatchCallback // may be nil
3232
// Pwd is the working directory for resolving relative paths
3333
Pwd string
34+
// Simplified indicates whether to use the simplified input schema.
35+
// Helpful for weaker models.
36+
Simplified bool
3437
// ClipboardEnabled controls whether clipboard functionality is enabled.
38+
// Ignored if Simplified is true.
3539
// NB: The actual implementation of the patch tool is unchanged,
3640
// this flag merely extends the description and input schema to include the clipboard operations.
3741
ClipboardEnabled bool
@@ -43,7 +47,10 @@ type PatchTool struct {
4347
func (p *PatchTool) Tool() *llm.Tool {
4448
description := PatchBaseDescription + PatchUsageNotes
4549
schema := PatchStandardInputSchema
46-
if p.ClipboardEnabled {
50+
switch {
51+
case p.Simplified:
52+
schema = PatchStandardSimplifiedSchema
53+
case p.ClipboardEnabled:
4754
description = PatchBaseDescription + PatchClipboardDescription + PatchUsageNotes
4855
schema = PatchClipboardInputSchema
4956
}
@@ -130,6 +137,36 @@ Usage notes:
130137
}
131138
`
132139

140+
PatchStandardSimplifiedSchema = `{
141+
"type": "object",
142+
"required": ["path", "patch"],
143+
"properties": {
144+
"path": {
145+
"type": "string",
146+
"description": "Path to the file to patch"
147+
},
148+
"patch": {
149+
"type": "object",
150+
"required": ["operation", "newText"],
151+
"properties": {
152+
"operation": {
153+
"type": "string",
154+
"enum": ["replace", "append_eof", "prepend_bof", "overwrite"],
155+
"description": "Type of operation to perform"
156+
},
157+
"oldText": {
158+
"type": "string",
159+
"description": "Text to locate for the operation (must be unique in file, required for replace)"
160+
},
161+
"newText": {
162+
"type": "string",
163+
"description": "The new text to use (empty for deletions)"
164+
}
165+
}
166+
}
167+
}
168+
}`
169+
133170
PatchClipboardInputSchema = `
134171
{
135172
"type": "object",
@@ -199,8 +236,14 @@ type PatchInput struct {
199236

200237
// PatchInputOne is a simplified version of PatchInput for single patch operations.
201238
type PatchInputOne struct {
202-
Path string `json:"path"`
203-
Patches PatchRequest `json:"patches"`
239+
Path string `json:"path"`
240+
Patches *PatchRequest `json:"patches"`
241+
}
242+
243+
// PatchInputOneSingular is PatchInputOne with a better name for the singular case.
244+
type PatchInputOneSingular struct {
245+
Path string `json:"path"`
246+
Patch *PatchRequest `json:"patch"`
204247
}
205248

206249
type PatchInputOneString struct {
@@ -253,17 +296,21 @@ func (p *PatchTool) Run(ctx context.Context, m json.RawMessage) llm.ToolOut {
253296
func (p *PatchTool) patchParse(m json.RawMessage) (PatchInput, error) {
254297
var input PatchInput
255298
originalErr := json.Unmarshal(m, &input)
256-
if originalErr == nil {
299+
if originalErr == nil && len(input.Patches) > 0 {
257300
return input, nil
258301
}
259302
var inputOne PatchInputOne
260-
if err := json.Unmarshal(m, &inputOne); err == nil {
261-
return PatchInput{Path: inputOne.Path, Patches: []PatchRequest{inputOne.Patches}}, nil
303+
if err := json.Unmarshal(m, &inputOne); err == nil && inputOne.Patches != nil {
304+
return PatchInput{Path: inputOne.Path, Patches: []PatchRequest{*inputOne.Patches}}, nil
305+
}
306+
var inputOneSingular PatchInputOneSingular
307+
if err := json.Unmarshal(m, &inputOneSingular); err == nil && inputOneSingular.Patch != nil {
308+
return PatchInput{Path: inputOneSingular.Path, Patches: []PatchRequest{*inputOneSingular.Patch}}, nil
262309
}
263310
var inputOneString PatchInputOneString
264311
if err := json.Unmarshal(m, &inputOneString); err == nil {
265312
var onePatch PatchRequest
266-
if err := json.Unmarshal([]byte(inputOneString.Patches), &onePatch); err == nil {
313+
if err := json.Unmarshal([]byte(inputOneString.Patches), &onePatch); err == nil && onePatch.Operation != "" {
267314
return PatchInput{Path: inputOneString.Path, Patches: []PatchRequest{onePatch}}, nil
268315
}
269316
var patches []PatchRequest

claudetool/patch_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ func TestPatchTool_FlexibleInputParsing(t *testing.T) {
320320
// Test single patch format (PatchInputOne)
321321
inputOne := PatchInputOne{
322322
Path: testFile,
323-
Patches: PatchRequest{
323+
Patches: &PatchRequest{
324324
Operation: "overwrite",
325325
NewText: "Single patch format\n",
326326
},

llm/ant/ant.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,3 +565,8 @@ func (s *Service) Do(ctx context.Context, ir *llm.Request) (*llm.Response, error
565565
}
566566
}
567567
}
568+
569+
// For debugging only, Claude can definitely handle the full patch tool.
570+
// func (s *Service) UseSimplifiedPatch() bool {
571+
// return true
572+
// }

llm/llm.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ type Service interface {
2121
TokenContextWindow() int
2222
}
2323

24+
type SimplifiedPatcher interface {
25+
// UseSimplifiedPatch reports whether the service should use the simplified patch input schema.
26+
UseSimplifiedPatch() bool
27+
}
28+
29+
func UseSimplifiedPatch(svc Service) bool {
30+
if sp, ok := svc.(SimplifiedPatcher); ok {
31+
return sp.UseSimplifiedPatch()
32+
}
33+
return false
34+
}
35+
2436
// MustSchema validates that schema is a valid JSON schema and returns it as a json.RawMessage.
2537
// It panics if the schema is invalid.
2638
// The schema must have at least type="object" and a properties key.

llm/oai/oai.go

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ const (
3737
)
3838

3939
type Model struct {
40-
UserName string // provided by the user to identify this model (e.g. "gpt4.1")
41-
ModelName string // provided to the service provide to specify which model to use (e.g. "gpt-4.1-2025-04-14")
42-
URL string
43-
APIKeyEnv string // environment variable name for the API key
44-
IsReasoningModel bool // whether this model is a reasoning model (e.g. O3, O4-mini)
40+
UserName string // provided by the user to identify this model (e.g. "gpt4.1")
41+
ModelName string // provided to the service provide to specify which model to use (e.g. "gpt-4.1-2025-04-14")
42+
URL string
43+
APIKeyEnv string // environment variable name for the API key
44+
IsReasoningModel bool // whether this model is a reasoning model (e.g. O3, O4-mini)
45+
UseSimplifiedPatch bool // whether to use the simplified patch input schema; defaults to false
4546
}
4647

4748
var (
@@ -210,17 +211,19 @@ var (
210211
}
211212

212213
Qwen3CoderFireworks = Model{
213-
UserName: "qwen3-coder-fireworks",
214-
ModelName: "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct",
215-
URL: FireworksURL,
216-
APIKeyEnv: FireworksAPIKeyEnv,
214+
UserName: "qwen3-coder-fireworks",
215+
ModelName: "accounts/fireworks/models/qwen3-coder-480b-a35b-instruct",
216+
URL: FireworksURL,
217+
APIKeyEnv: FireworksAPIKeyEnv,
218+
UseSimplifiedPatch: true,
217219
}
218220

219221
// Qwen is a skaband-specific model name for Qwen3-Coder
220222
// Provider details (URL and APIKeyEnv) are handled by skaband
221223
Qwen = Model{
222-
UserName: "qwen",
223-
ModelName: "qwen", // skaband will map this to the actual provider model
224+
UserName: "qwen",
225+
ModelName: "qwen", // skaband will map this to the actual provider model
226+
UseSimplifiedPatch: true,
224227
}
225228
)
226229

@@ -783,3 +786,7 @@ func (s *Service) Do(ctx context.Context, ir *llm.Request) (*llm.Response, error
783786
}
784787
}
785788
}
789+
790+
func (s *Service) UseSimplifiedPatch() bool {
791+
return s.Model.UseSimplifiedPatch
792+
}

loop/agent.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,6 +1394,7 @@ func (a *Agent) initConvoWithUsage(usage *conversation.CumulativeUsage) *convers
13941394
patchTool := &claudetool.PatchTool{
13951395
Callback: a.patchCallback,
13961396
Pwd: a.workingDir,
1397+
Simplified: llm.UseSimplifiedPatch(a.config.Service),
13971398
ClipboardEnabled: experiment.Enabled("clipboard"),
13981399
}
13991400

0 commit comments

Comments
 (0)