Skip to content

Commit 5191862

Browse files
authored
Merge pull request #2388 from dgageot/board/fix-docker-agent-issue-2383-81b49d7b
fix: prevent panic in code mode when tool handler is nil
2 parents f96edc1 + 0f4b014 commit 5191862

File tree

3 files changed

+55
-68
lines changed

3 files changed

+55
-68
lines changed

pkg/toolinstall/resolver.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,23 +25,14 @@ func EnsureCommand(ctx context.Context, command, version string) (string, error)
2525
return command, nil
2626
}
2727

28-
version = strings.TrimSpace(version)
29-
lower := strings.ToLower(version)
28+
lower := strings.ToLower(strings.TrimSpace(version))
3029
if lower == "false" || lower == "off" {
3130
return command, nil
3231
}
3332

3433
resolvedPath, err := resolve(ctx, command, version)
3534
if err != nil {
36-
if version != "" {
37-
return "", fmt.Errorf("auto-installing command %q: %w", command, err)
38-
}
39-
40-
slog.Warn("Auto-install failed, falling back to original command",
41-
"command", command,
42-
"error", err,
43-
)
44-
return command, nil
35+
return "", fmt.Errorf("auto-installing command %q: %w", command, err)
4536
}
4637

4738
return resolvedPath, nil

pkg/toolinstall/resolver_test.go

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,7 @@ func TestEnsureCommand_DisabledPerToolset(t *testing.T) {
5050
}
5151
}
5252

53-
func TestEnsureCommand_AutoDetectFailureFallsBackToOriginalCommand(t *testing.T) {
54-
t.Setenv("DOCKER_AGENT_TOOLS_DIR", t.TempDir())
55-
t.Setenv("DOCKER_AGENT_AUTO_INSTALL", "")
56-
57-
result, err := EnsureCommand(t.Context(), "nonexistent-tool", "")
58-
require.NoError(t, err)
59-
assert.Equal(t, "nonexistent-tool", result)
60-
}
61-
62-
func TestEnsureCommand_ExplicitVersionFailureStillErrors(t *testing.T) {
53+
func TestEnsureCommand_SoftFail(t *testing.T) {
6354
t.Setenv("DOCKER_AGENT_TOOLS_DIR", t.TempDir())
6455
t.Setenv("DOCKER_AGENT_AUTO_INSTALL", "")
6556

@@ -82,7 +73,7 @@ func TestEnsureCommand_FoundInBinDir(t *testing.T) {
8273
assert.Equal(t, fakeBin, result)
8374
}
8475

85-
func TestEnsureCommand_NonExecutableInBinDirFallsBackToOriginalCommand(t *testing.T) {
76+
func TestEnsureCommand_NonExecutableInBinDirIsSkipped(t *testing.T) {
8677
toolsDir := t.TempDir()
8778
t.Setenv("DOCKER_AGENT_TOOLS_DIR", toolsDir)
8879
t.Setenv("DOCKER_AGENT_AUTO_INSTALL", "")
@@ -91,9 +82,9 @@ func TestEnsureCommand_NonExecutableInBinDirFallsBackToOriginalCommand(t *testin
9182
require.NoError(t, os.MkdirAll(binDir, 0o755))
9283
require.NoError(t, os.WriteFile(filepath.Join(binDir, "not-executable"), []byte("data"), 0o644))
9384

94-
result, err := EnsureCommand(t.Context(), "not-executable", "")
95-
require.NoError(t, err)
96-
assert.Equal(t, "not-executable", result)
85+
// Falls through to auto-install → fails → returns error.
86+
_, err := EnsureCommand(t.Context(), "not-executable", "")
87+
require.Error(t, err)
9788
}
9889

9990
// --- resolve tests ---

pkg/tools/codemode/exec.go

Lines changed: 48 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -89,57 +89,62 @@ func (c *codeModeTool) runJavascript(ctx context.Context, script string) (Script
8989

9090
func callTool(ctx context.Context, tool tools.Tool, tracker *toolCallTracker) func(args map[string]any) (string, error) {
9191
return func(args map[string]any) (string, error) {
92-
var toolArgs struct {
93-
Required []string `json:"required"`
94-
}
95-
96-
if err := tools.ConvertSchema(tool.Parameters, &toolArgs); err != nil {
97-
tracker.record(ToolCallInfo{
98-
Name: tool.Name,
99-
Arguments: args,
100-
Error: err.Error(),
101-
})
102-
return "", err
103-
}
92+
output, filtered, err := invokeTool(ctx, tool, args)
10493

105-
nonNilArgs := make(map[string]any)
106-
for k, v := range args {
107-
if slices.Contains(toolArgs.Required, k) || v != nil {
108-
nonNilArgs[k] = v
109-
}
94+
info := ToolCallInfo{
95+
Name: tool.Name,
96+
Arguments: filtered,
11097
}
111-
112-
arguments, err := json.Marshal(nonNilArgs)
11398
if err != nil {
114-
tracker.record(ToolCallInfo{
115-
Name: tool.Name,
116-
Arguments: nonNilArgs,
117-
Error: err.Error(),
118-
})
119-
return "", err
99+
info.Error = err.Error()
100+
} else {
101+
info.Result = output
120102
}
103+
tracker.record(info)
121104

122-
result, err := tool.Handler(ctx, tools.ToolCall{
123-
Function: tools.FunctionCall{
124-
Name: tool.Name,
125-
Arguments: string(arguments),
126-
},
127-
})
128-
if err != nil {
129-
tracker.record(ToolCallInfo{
130-
Name: tool.Name,
131-
Arguments: nonNilArgs,
132-
Error: err.Error(),
133-
})
134-
return "", err
105+
return output, err
106+
}
107+
}
108+
109+
// invokeTool calls a single tool handler, filtering out nil optional arguments.
110+
// It returns the output, the filtered arguments actually sent, and any error.
111+
func invokeTool(ctx context.Context, tool tools.Tool, args map[string]any) (string, map[string]any, error) {
112+
if tool.Handler == nil {
113+
return "", args, fmt.Errorf("tool %q is not available in code mode", tool.Name)
114+
}
115+
116+
var schema struct {
117+
Required []string `json:"required"`
118+
}
119+
if err := tools.ConvertSchema(tool.Parameters, &schema); err != nil {
120+
return "", args, err
121+
}
122+
123+
// Strip nil optional arguments that goja passes for omitted parameters.
124+
filtered := make(map[string]any)
125+
for k, v := range args {
126+
if slices.Contains(schema.Required, k) || v != nil {
127+
filtered[k] = v
135128
}
129+
}
136130

137-
tracker.record(ToolCallInfo{
131+
arguments, err := json.Marshal(filtered)
132+
if err != nil {
133+
return "", filtered, err
134+
}
135+
136+
result, err := tool.Handler(ctx, tools.ToolCall{
137+
Function: tools.FunctionCall{
138138
Name: tool.Name,
139-
Arguments: nonNilArgs,
140-
Result: result.Output,
141-
})
139+
Arguments: string(arguments),
140+
},
141+
})
142+
if err != nil {
143+
return "", filtered, err
144+
}
142145

143-
return result.Output, nil
146+
if result == nil {
147+
return "", filtered, nil
144148
}
149+
return result.Output, filtered, nil
145150
}

0 commit comments

Comments
 (0)