config: add tools bool to disable tool collection per agent

When tools is false in agent config, BuildAgentEnv skips ListTools
entirely so no tool schemas are sent to the backend. Models without
tool support now work cleanly.
This commit is contained in:
Levi Neely 2026-05-03 14:58:42 +02:00
parent 1b04d6a75b
commit c069deccca
4 changed files with 51 additions and 9 deletions

View File

@ -47,18 +47,22 @@ type AgentEnv struct {
func BuildAgentEnv(cfg *config.Config, d tools.Dispatcher, cwd string) AgentEnv {
var messages []string
allToolInfos, listErr := d.ListTools()
if listErr != nil {
messages = append(messages, fmt.Sprintf("list tools: %v", listErr))
}
var allToolInfos []tools.ToolInfo
var allTools []backend.Tool
serverOf := make(map[string]string)
serverOf := make(map[string]string, len(allToolInfos))
for _, t := range allToolInfos {
serverOf[t.Name] = t.Server
if cfg == nil || cfg.ToolsEnabled() {
var listErr error
allToolInfos, listErr = d.ListTools()
if listErr != nil {
messages = append(messages, fmt.Sprintf("list tools: %v", listErr))
}
for _, t := range allToolInfos {
serverOf[t.Name] = t.Server
}
allTools = toolInfosToBackend(allToolInfos)
}
allTools := toolInfosToBackend(allToolInfos)
hooks := Hooks{}
var preamble string
var genParams backend.GenerationParams

View File

@ -2335,6 +2335,19 @@ func TestBuildAgentEnv_ToolsPopulated(t *testing.T) {
}
}
func TestBuildAgentEnv_ToolsDisabled(t *testing.T) {
setupCfgDir(t)
d := &mockDispatcher{
tools: []tools.ToolInfo{{Server: "s1", Name: "mytool", Description: "desc", InputSchema: json.RawMessage(`{}`)}},
}
f := false
cfg := &config.Config{Tools: &f}
env := BuildAgentEnv(cfg, d, t.TempDir())
if len(env.tools) != 0 {
t.Errorf("expected no tools when disabled; got %+v", env.tools)
}
}
func TestBuildAgentEnv_TrustedTools(t *testing.T) {
setupCfgDir(t)
setter := &mockTrustedSetter{}

View File

@ -61,6 +61,7 @@ func (p Prompt) MarshalJSON() ([]byte, error) {
type Config struct {
Hooks map[string]HookCmds `json:"hooks,omitempty"`
Prompt Prompt `json:"prompt,omitempty"`
Tools *bool `json:"tools,omitempty"`
TrustedTools []string `json:"trustedTools,omitempty"`
MaxTokens int `json:"maxTokens,omitempty"`
Temperature *float64 `json:"temperature,omitempty"`
@ -76,3 +77,9 @@ func Load(r io.Reader) (*Config, error) {
}
return &cfg, nil
}
// ToolsEnabled reports whether tool use is enabled. Defaults to true when
// the field is omitted from the config.
func (c *Config) ToolsEnabled() bool {
return c.Tools == nil || *c.Tools
}

View File

@ -121,3 +121,21 @@ func TestLoadAllFields(t *testing.T) {
t.Errorf("PresencePenalty = %v", cfg.PresencePenalty)
}
}
func TestToolsEnabled(t *testing.T) {
// Omitted: defaults to true.
cfg, _ := Load(strings.NewReader(`{}`))
if !cfg.ToolsEnabled() {
t.Error("expected ToolsEnabled()=true when omitted")
}
// Explicit false.
cfg, _ = Load(strings.NewReader(`{"tools": false}`))
if cfg.ToolsEnabled() {
t.Error("expected ToolsEnabled()=false")
}
// Explicit true.
cfg, _ = Load(strings.NewReader(`{"tools": true}`))
if !cfg.ToolsEnabled() {
t.Error("expected ToolsEnabled()=true")
}
}