Skip to content

Commit fde430e

Browse files
dgageotkrissetto
authored andcommitted
Hide tools comfiguration
Signed-off-by: David Gageot <david.gageot@docker.com>
1 parent e80b038 commit fde430e

File tree

6 files changed

+239
-10
lines changed

6 files changed

+239
-10
lines changed

cmd/root/alias.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,9 @@ func newAliasCmd() *cobra.Command {
4242
}
4343

4444
type aliasAddFlags struct {
45-
yolo bool
46-
model string
45+
yolo bool
46+
model string
47+
hideToolResults bool
4748
}
4849

4950
func newAliasAddCmd() *cobra.Command {
@@ -57,8 +58,9 @@ func newAliasAddCmd() *cobra.Command {
5758
You can optionally specify runtime options that will be applied whenever
5859
the alias is used:
5960
60-
--yolo Automatically approve all tool calls without prompting
61-
--model Override the agent's model (format: [agent=]provider/model)`,
61+
--yolo Automatically approve all tool calls without prompting
62+
--model Override the agent's model (format: [agent=]provider/model)
63+
--hide-tool-results Hide tool call results in the TUI`,
6264
Example: ` # Create a simple alias
6365
cagent alias add code agentcatalog/notion-expert
6466
@@ -68,7 +70,10 @@ the alias is used:
6870
# Create an alias with a specific model
6971
cagent alias add fast-coder agentcatalog/coder --model openai/gpt-4o-mini
7072
71-
# Create an alias with both options
73+
# Create an alias with hidden tool results
74+
cagent alias add quiet agentcatalog/coder --hide-tool-results
75+
76+
# Create an alias with multiple options
7277
cagent alias add turbo agentcatalog/coder --yolo --model anthropic/claude-sonnet-4-0`,
7378
Args: cobra.ExactArgs(2),
7479
RunE: func(cmd *cobra.Command, args []string) error {
@@ -78,6 +83,7 @@ the alias is used:
7883

7984
cmd.Flags().BoolVar(&flags.yolo, "yolo", false, "Automatically approve all tool calls without prompting")
8085
cmd.Flags().StringVar(&flags.model, "model", "", "Override agent model (format: [agent=]provider/model)")
86+
cmd.Flags().BoolVar(&flags.hideToolResults, "hide-tool-results", false, "Hide tool call results in the TUI")
8187

8288
return cmd
8389
}
@@ -123,9 +129,10 @@ func runAliasAddCommand(cmd *cobra.Command, args []string, flags *aliasAddFlags)
123129

124130
// Create alias with options
125131
alias := &userconfig.Alias{
126-
Path: absAgentPath,
127-
Yolo: flags.yolo,
128-
Model: flags.model,
132+
Path: absAgentPath,
133+
Yolo: flags.yolo,
134+
Model: flags.model,
135+
HideToolResults: flags.hideToolResults,
129136
}
130137

131138
// Store the alias
@@ -147,6 +154,9 @@ func runAliasAddCommand(cmd *cobra.Command, args []string, flags *aliasAddFlags)
147154
if flags.model != "" {
148155
out.Printf(" Model: %s\n", flags.model)
149156
}
157+
if flags.hideToolResults {
158+
out.Printf(" Hide tool results: enabled\n")
159+
}
150160

151161
if name == "default" {
152162
out.Printf("\nYou can now run: cagent run %s (or even cagent run)\n", name)
@@ -201,6 +211,9 @@ func runAliasListCommand(cmd *cobra.Command, args []string) error {
201211
if alias.Model != "" {
202212
options = append(options, "model="+alias.Model)
203213
}
214+
if alias.HideToolResults {
215+
options = append(options, "hide-tool-results")
216+
}
204217

205218
if len(options) > 0 {
206219
out.Printf(" %s%s → %s [%s]\n", name, padding, alias.Path, strings.Join(options, ", "))

cmd/root/run.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,16 +152,27 @@ func (f *runExecFlags) runOrExec(ctx context.Context, out *cli.Printer, args []s
152152
agentFileName = args[0]
153153
}
154154

155+
// Apply global user settings first (lowest priority)
156+
// User settings only apply if the flag wasn't explicitly set by the user
157+
userSettings := config.GetUserSettings()
158+
if userSettings.HideToolResults && !f.hideToolResults {
159+
f.hideToolResults = true
160+
slog.Debug("Applying user settings", "hide_tool_results", true)
161+
}
162+
155163
// Apply alias options if this is an alias reference
156164
// Alias options only apply if the flag wasn't explicitly set by the user
157165
if alias := config.ResolveAlias(agentFileName); alias != nil {
158-
slog.Debug("Applying alias options", "yolo", alias.Yolo, "model", alias.Model)
166+
slog.Debug("Applying alias options", "yolo", alias.Yolo, "model", alias.Model, "hide_tool_results", alias.HideToolResults)
159167
if alias.Yolo && !f.autoApprove {
160168
f.autoApprove = true
161169
}
162170
if alias.Model != "" && len(f.modelOverrides) == 0 {
163171
f.modelOverrides = append(f.modelOverrides, alias.Model)
164172
}
173+
if alias.HideToolResults && !f.hideToolResults {
174+
f.hideToolResults = true
175+
}
165176
}
166177

167178
// Start fake proxy if --fake is specified

pkg/config/resolve.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ func ResolveAlias(agentFilename string) *userconfig.Alias {
3636
return alias
3737
}
3838

39+
// GetUserSettings returns the global user settings from the config file.
40+
// Returns an empty Settings if the config file doesn't exist or has no settings.
41+
func GetUserSettings() *userconfig.Settings {
42+
cfg, err := userconfig.Load()
43+
if err != nil {
44+
return &userconfig.Settings{}
45+
}
46+
return cfg.GetSettings()
47+
}
48+
3949
// ResolveSources resolves an agent file reference (local file, URL, or OCI image) to sources
4050
// For OCI references, always checks remote for updates but falls back to local cache if offline
4151
func ResolveSources(agentsPath string) (Sources, error) {

pkg/config/resolve_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,3 +544,75 @@ func TestResolveAlias_EmptyUsesDefault(t *testing.T) {
544544
require.NotNil(t, alias)
545545
assert.True(t, alias.Yolo)
546546
}
547+
548+
func TestResolveAlias_WithHideToolResultsOption(t *testing.T) {
549+
home := t.TempDir()
550+
t.Setenv("HOME", home)
551+
552+
// Set up alias with hide_tool_results option
553+
cfg, err := userconfig.Load()
554+
require.NoError(t, err)
555+
require.NoError(t, cfg.SetAlias("hidden-tools", &userconfig.Alias{
556+
Path: "agentcatalog/coder",
557+
HideToolResults: true,
558+
}))
559+
require.NoError(t, cfg.Save())
560+
561+
// Resolve alias options
562+
alias := ResolveAlias("hidden-tools")
563+
require.NotNil(t, alias)
564+
assert.True(t, alias.HideToolResults)
565+
assert.False(t, alias.Yolo)
566+
assert.Empty(t, alias.Model)
567+
}
568+
569+
func TestResolveAlias_WithAllOptions(t *testing.T) {
570+
home := t.TempDir()
571+
t.Setenv("HOME", home)
572+
573+
// Set up alias with all options
574+
cfg, err := userconfig.Load()
575+
require.NoError(t, err)
576+
require.NoError(t, cfg.SetAlias("full", &userconfig.Alias{
577+
Path: "agentcatalog/coder",
578+
Yolo: true,
579+
Model: "anthropic/claude-sonnet-4-0",
580+
HideToolResults: true,
581+
}))
582+
require.NoError(t, cfg.Save())
583+
584+
// Resolve alias options
585+
alias := ResolveAlias("full")
586+
require.NotNil(t, alias)
587+
assert.True(t, alias.Yolo)
588+
assert.Equal(t, "anthropic/claude-sonnet-4-0", alias.Model)
589+
assert.True(t, alias.HideToolResults)
590+
}
591+
592+
func TestGetUserSettings_Empty(t *testing.T) {
593+
home := t.TempDir()
594+
t.Setenv("HOME", home)
595+
596+
// No config file exists
597+
settings := GetUserSettings()
598+
require.NotNil(t, settings)
599+
assert.False(t, settings.HideToolResults)
600+
}
601+
602+
func TestGetUserSettings_WithHideToolResults(t *testing.T) {
603+
home := t.TempDir()
604+
t.Setenv("HOME", home)
605+
606+
// Set up config with settings
607+
cfg, err := userconfig.Load()
608+
require.NoError(t, err)
609+
cfg.Settings = &userconfig.Settings{
610+
HideToolResults: true,
611+
}
612+
require.NoError(t, cfg.Save())
613+
614+
// Get settings
615+
settings := GetUserSettings()
616+
require.NotNil(t, settings)
617+
assert.True(t, settings.HideToolResults)
618+
}

pkg/userconfig/userconfig.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,19 @@ type Alias struct {
2626
Yolo bool `yaml:"yolo,omitempty"`
2727
// Model overrides the agent's model (format: [agent=]provider/model)
2828
Model string `yaml:"model,omitempty"`
29+
// HideToolResults hides tool call results in the TUI
30+
HideToolResults bool `yaml:"hide_tool_results,omitempty"`
2931
}
3032

3133
// HasOptions returns true if the alias has any runtime options set
3234
func (a *Alias) HasOptions() bool {
33-
return a != nil && (a.Yolo || a.Model != "")
35+
return a != nil && (a.Yolo || a.Model != "" || a.HideToolResults)
36+
}
37+
38+
// Settings represents global user settings
39+
type Settings struct {
40+
// HideToolResults hides tool call results in the TUI by default
41+
HideToolResults bool `yaml:"hide_tool_results,omitempty"`
3442
}
3543

3644
// CurrentVersion is the current version of the user config format
@@ -44,6 +52,8 @@ type Config struct {
4452
ModelsGateway string `yaml:"models_gateway,omitempty"`
4553
// Aliases maps alias names to alias configurations
4654
Aliases map[string]*Alias `yaml:"aliases,omitempty"`
55+
// Settings contains global user settings
56+
Settings *Settings `yaml:"settings,omitempty"`
4757
}
4858

4959
// Path returns the path to the config file
@@ -206,3 +216,11 @@ func (c *Config) DeleteAlias(name string) bool {
206216
}
207217
return false
208218
}
219+
220+
// GetSettings returns the global settings, or an empty Settings if not set
221+
func (c *Config) GetSettings() *Settings {
222+
if c.Settings == nil {
223+
return &Settings{}
224+
}
225+
return c.Settings
226+
}

pkg/userconfig/userconfig_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -485,3 +485,108 @@ func TestConfig_Version_LoadLegacyWithoutVersion(t *testing.T) {
485485
require.NoError(t, config.saveTo(configFile))
486486
assert.Equal(t, CurrentVersion, config.Version)
487487
}
488+
489+
func TestConfig_Settings_HideToolResults(t *testing.T) {
490+
t.Parallel()
491+
492+
tmpDir := t.TempDir()
493+
configFile := filepath.Join(tmpDir, "config.yaml")
494+
495+
config := &Config{
496+
Settings: &Settings{
497+
HideToolResults: true,
498+
},
499+
}
500+
501+
require.NoError(t, config.saveTo(configFile))
502+
503+
loaded, err := loadFrom(configFile, "")
504+
require.NoError(t, err)
505+
506+
assert.NotNil(t, loaded.Settings)
507+
assert.True(t, loaded.Settings.HideToolResults)
508+
}
509+
510+
func TestConfig_Settings_Empty(t *testing.T) {
511+
t.Parallel()
512+
513+
tmpDir := t.TempDir()
514+
configFile := filepath.Join(tmpDir, "config.yaml")
515+
516+
config, err := loadFrom(configFile, "")
517+
require.NoError(t, err)
518+
519+
// GetSettings should return an empty Settings struct, not nil
520+
settings := config.GetSettings()
521+
assert.NotNil(t, settings)
522+
assert.False(t, settings.HideToolResults)
523+
}
524+
525+
func TestConfig_Settings_GetSettingsNil(t *testing.T) {
526+
t.Parallel()
527+
528+
config := &Config{Aliases: make(map[string]*Alias)}
529+
530+
// GetSettings should return an empty Settings struct when Settings is nil
531+
settings := config.GetSettings()
532+
assert.NotNil(t, settings)
533+
assert.False(t, settings.HideToolResults)
534+
}
535+
536+
func TestConfig_AliasWithHideToolResults(t *testing.T) {
537+
t.Parallel()
538+
539+
tmpDir := t.TempDir()
540+
configFile := filepath.Join(tmpDir, "config.yaml")
541+
542+
config := &Config{
543+
Aliases: map[string]*Alias{
544+
"hidden": {Path: "agentcatalog/coder", HideToolResults: true},
545+
"full": {Path: "agentcatalog/coder", Yolo: true, Model: "openai/gpt-4o", HideToolResults: true},
546+
},
547+
}
548+
549+
require.NoError(t, config.saveTo(configFile))
550+
551+
loaded, err := loadFrom(configFile, "")
552+
require.NoError(t, err)
553+
554+
// Verify hide_tool_results option
555+
hiddenAlias, ok := loaded.GetAlias("hidden")
556+
require.True(t, ok)
557+
assert.Equal(t, "agentcatalog/coder", hiddenAlias.Path)
558+
assert.True(t, hiddenAlias.HideToolResults)
559+
assert.False(t, hiddenAlias.Yolo)
560+
assert.Empty(t, hiddenAlias.Model)
561+
562+
// Verify all options together
563+
fullAlias, ok := loaded.GetAlias("full")
564+
require.True(t, ok)
565+
assert.True(t, fullAlias.HideToolResults)
566+
assert.True(t, fullAlias.Yolo)
567+
assert.Equal(t, "openai/gpt-4o", fullAlias.Model)
568+
}
569+
570+
func TestAlias_HasOptions(t *testing.T) {
571+
t.Parallel()
572+
573+
tests := []struct {
574+
name string
575+
alias *Alias
576+
expected bool
577+
}{
578+
{"nil alias", nil, false},
579+
{"empty alias", &Alias{Path: "test"}, false},
580+
{"yolo only", &Alias{Path: "test", Yolo: true}, true},
581+
{"model only", &Alias{Path: "test", Model: "openai/gpt-4o"}, true},
582+
{"hide_tool_results only", &Alias{Path: "test", HideToolResults: true}, true},
583+
{"all options", &Alias{Path: "test", Yolo: true, Model: "openai/gpt-4o", HideToolResults: true}, true},
584+
}
585+
586+
for _, tt := range tests {
587+
t.Run(tt.name, func(t *testing.T) {
588+
t.Parallel()
589+
assert.Equal(t, tt.expected, tt.alias.HasOptions())
590+
})
591+
}
592+
}

0 commit comments

Comments
 (0)