feat(config): add allowed_tools/disallowed_tools support for claude backend

- Add AllowedTools/DisallowedTools fields to AgentModelConfig and Config
- Update ResolveAgentConfig to return new fields
- Pass --allowedTools/--disallowedTools to claude CLI in buildClaudeArgs
- Add fields to TaskSpec and propagate through executor
- Fix backend selection when taskSpec.Backend is specified but backend=nil

Generated with SWE-Agent.ai

Co-Authored-By: SWE-Agent.ai <noreply@swe-agent.ai>
This commit is contained in:
cexll
2026-02-03 16:11:25 +08:00
parent c0f61d5cc2
commit 04fa1626ae
9 changed files with 67 additions and 33 deletions

View File

@@ -16,14 +16,16 @@ type BackendConfig struct {
}
type AgentModelConfig struct {
Backend string `json:"backend"`
Model string `json:"model"`
PromptFile string `json:"prompt_file,omitempty"`
Description string `json:"description,omitempty"`
Yolo bool `json:"yolo,omitempty"`
Reasoning string `json:"reasoning,omitempty"`
BaseURL string `json:"base_url,omitempty"`
APIKey string `json:"api_key,omitempty"`
Backend string `json:"backend"`
Model string `json:"model"`
PromptFile string `json:"prompt_file,omitempty"`
Description string `json:"description,omitempty"`
Yolo bool `json:"yolo,omitempty"`
Reasoning string `json:"reasoning,omitempty"`
BaseURL string `json:"base_url,omitempty"`
APIKey string `json:"api_key,omitempty"`
AllowedTools []string `json:"allowed_tools,omitempty"`
DisallowedTools []string `json:"disallowed_tools,omitempty"`
}
type ModelsConfig struct {
@@ -178,17 +180,17 @@ func resolveBackendConfig(cfg *ModelsConfig, backendName string) BackendConfig {
return BackendConfig{}
}
func resolveAgentConfig(agentName string) (backend, model, promptFile, reasoning, baseURL, apiKey string, yolo bool, err error) {
func resolveAgentConfig(agentName string) (backend, model, promptFile, reasoning, baseURL, apiKey string, yolo bool, allowedTools, disallowedTools []string, err error) {
if err := ValidateAgentName(agentName); err != nil {
return "", "", "", "", "", "", false, err
return "", "", "", "", "", "", false, nil, nil, err
}
cfg, err := modelsConfig()
if err != nil {
return "", "", "", "", "", "", false, err
return "", "", "", "", "", "", false, nil, nil, err
}
if cfg == nil {
return "", "", "", "", "", "", false, fmt.Errorf("models config is nil\n\n%s", modelsConfigHint(""))
return "", "", "", "", "", "", false, nil, nil, fmt.Errorf("models config is nil\n\n%s", modelsConfigHint(""))
}
if agent, ok := cfg.Agents[agentName]; ok {
@@ -198,9 +200,9 @@ func resolveAgentConfig(agentName string) (backend, model, promptFile, reasoning
if backend == "" {
configPath, pathErr := modelsConfigPath()
if pathErr != nil {
return "", "", "", "", "", "", false, fmt.Errorf("agent %q has empty backend and default_backend is not set\n\n%s", agentName, modelsConfigHint(""))
return "", "", "", "", "", "", false, nil, nil, fmt.Errorf("agent %q has empty backend and default_backend is not set\n\n%s", agentName, modelsConfigHint(""))
}
return "", "", "", "", "", "", false, fmt.Errorf("agent %q has empty backend and default_backend is not set\n\n%s", agentName, modelsConfigHint(configPath))
return "", "", "", "", "", "", false, nil, nil, fmt.Errorf("agent %q has empty backend and default_backend is not set\n\n%s", agentName, modelsConfigHint(configPath))
}
}
backendCfg := resolveBackendConfig(cfg, backend)
@@ -218,11 +220,11 @@ func resolveAgentConfig(agentName string) (backend, model, promptFile, reasoning
if model == "" {
configPath, pathErr := modelsConfigPath()
if pathErr != nil {
return "", "", "", "", "", "", false, fmt.Errorf("agent %q has empty model; set agents.%s.model in %s\n\n%s", agentName, agentName, modelsConfigTildePath, modelsConfigHint(""))
return "", "", "", "", "", "", false, nil, nil, fmt.Errorf("agent %q has empty model; set agents.%s.model in %s\n\n%s", agentName, agentName, modelsConfigTildePath, modelsConfigHint(""))
}
return "", "", "", "", "", "", false, fmt.Errorf("agent %q has empty model; set agents.%s.model in %s\n\n%s", agentName, agentName, modelsConfigTildePath, modelsConfigHint(configPath))
return "", "", "", "", "", "", false, nil, nil, fmt.Errorf("agent %q has empty model; set agents.%s.model in %s\n\n%s", agentName, agentName, modelsConfigTildePath, modelsConfigHint(configPath))
}
return backend, model, agent.PromptFile, agent.Reasoning, baseURL, apiKey, agent.Yolo, nil
return backend, model, agent.PromptFile, agent.Reasoning, baseURL, apiKey, agent.Yolo, agent.AllowedTools, agent.DisallowedTools, nil
}
if dynamic, ok := LoadDynamicAgent(agentName); ok {
@@ -231,24 +233,24 @@ func resolveAgentConfig(agentName string) (backend, model, promptFile, reasoning
configPath, pathErr := modelsConfigPath()
if backend == "" || model == "" {
if pathErr != nil {
return "", "", "", "", "", "", false, fmt.Errorf("dynamic agent %q requires default_backend and default_model to be set in %s\n\n%s", agentName, modelsConfigTildePath, modelsConfigHint(""))
return "", "", "", "", "", "", false, nil, nil, fmt.Errorf("dynamic agent %q requires default_backend and default_model to be set in %s\n\n%s", agentName, modelsConfigTildePath, modelsConfigHint(""))
}
return "", "", "", "", "", "", false, fmt.Errorf("dynamic agent %q requires default_backend and default_model to be set in %s\n\n%s", agentName, modelsConfigTildePath, modelsConfigHint(configPath))
return "", "", "", "", "", "", false, nil, nil, fmt.Errorf("dynamic agent %q requires default_backend and default_model to be set in %s\n\n%s", agentName, modelsConfigTildePath, modelsConfigHint(configPath))
}
backendCfg := resolveBackendConfig(cfg, backend)
baseURL = strings.TrimSpace(backendCfg.BaseURL)
apiKey = strings.TrimSpace(backendCfg.APIKey)
return backend, model, dynamic.PromptFile, "", baseURL, apiKey, false, nil
return backend, model, dynamic.PromptFile, "", baseURL, apiKey, false, nil, nil, nil
}
configPath, pathErr := modelsConfigPath()
if pathErr != nil {
return "", "", "", "", "", "", false, fmt.Errorf("agent %q not found in %s\n\n%s", agentName, modelsConfigTildePath, modelsConfigHint(""))
return "", "", "", "", "", "", false, nil, nil, fmt.Errorf("agent %q not found in %s\n\n%s", agentName, modelsConfigTildePath, modelsConfigHint(""))
}
return "", "", "", "", "", "", false, fmt.Errorf("agent %q not found in %s\n\n%s", agentName, modelsConfigTildePath, modelsConfigHint(configPath))
return "", "", "", "", "", "", false, nil, nil, fmt.Errorf("agent %q not found in %s\n\n%s", agentName, modelsConfigTildePath, modelsConfigHint(configPath))
}
func ResolveAgentConfig(agentName string) (backend, model, promptFile, reasoning, baseURL, apiKey string, yolo bool, err error) {
func ResolveAgentConfig(agentName string) (backend, model, promptFile, reasoning, baseURL, apiKey string, yolo bool, allowedTools, disallowedTools []string, err error) {
return resolveAgentConfig(agentName)
}

View File

@@ -14,7 +14,7 @@ func TestResolveAgentConfig_NoConfig_ReturnsHelpfulError(t *testing.T) {
t.Cleanup(ResetModelsConfigCacheForTest)
ResetModelsConfigCacheForTest()
_, _, _, _, _, _, _, err := ResolveAgentConfig("develop")
_, _, _, _, _, _, _, _, _, err := ResolveAgentConfig("develop")
if err == nil {
t.Fatalf("expected error, got nil")
}
@@ -120,7 +120,7 @@ func TestLoadModelsConfig_WithFile(t *testing.T) {
t.Errorf("ResolveBackendConfig(apiKey) = %q, want %q", apiKey, "backend-key")
}
backend, model, _, _, agentBaseURL, agentAPIKey, _, err := ResolveAgentConfig("custom-agent")
backend, model, _, _, agentBaseURL, agentAPIKey, _, _, _, err := ResolveAgentConfig("custom-agent")
if err != nil {
t.Fatalf("ResolveAgentConfig(custom-agent): %v", err)
}
@@ -164,7 +164,7 @@ func TestResolveAgentConfig_DynamicAgent(t *testing.T) {
t.Fatalf("WriteFile: %v", err)
}
backend, model, promptFile, _, _, _, _, err := ResolveAgentConfig("sarsh")
backend, model, promptFile, _, _, _, _, _, _, err := ResolveAgentConfig("sarsh")
if err != nil {
t.Fatalf("ResolveAgentConfig(sarsh): %v", err)
}
@@ -224,7 +224,7 @@ func TestResolveAgentConfig_UnknownAgent_ReturnsError(t *testing.T) {
t.Fatalf("WriteFile: %v", err)
}
_, _, _, _, _, _, _, err := ResolveAgentConfig("unknown-agent")
_, _, _, _, _, _, _, _, _, err := ResolveAgentConfig("unknown-agent")
if err == nil {
t.Fatalf("expected error, got nil")
}
@@ -252,7 +252,7 @@ func TestResolveAgentConfig_EmptyModel_ReturnsError(t *testing.T) {
t.Fatalf("WriteFile: %v", err)
}
_, _, _, _, _, _, _, err := ResolveAgentConfig("bad-agent")
_, _, _, _, _, _, _, _, _, err := ResolveAgentConfig("bad-agent")
if err == nil {
t.Fatalf("expected error, got nil")
}

View File

@@ -24,6 +24,8 @@ type Config struct {
SkipPermissions bool
Yolo bool
MaxParallelWorkers int
AllowedTools []string
DisallowedTools []string
}
// EnvFlagEnabled returns true when the environment variable exists and is not