diff --git a/README.md b/README.md index 5dd30cfbec378da43693a9c3139ac12faeda4c16..5800edc5186010bb368bce8188d4948e62b52ad0 100644 --- a/README.md +++ b/README.md @@ -345,14 +345,17 @@ it creates. You can customize this behavior with the `attribution` option: "$schema": "https://charm.land/crush.json", "options": { "attribution": { - "co_authored_by": true, + "trailer_style": "co-authored-by", "generated_with": true } } } ``` -- `co_authored_by`: When true (default), adds `Co-Authored-By: Crush ` to commit messages +- `trailer_style`: Controls the attribution trailer added to commit messages (default: `co-authored-by`) + - `co-authored-by`: Adds `Co-Authored-By: Crush ` + - `assisted-by`: Adds `Assisted-by: [Model Name] via Crush` (includes the model name) + - `none`: No attribution trailer - `generated_with`: When true (default), adds `💘 Generated with Crush` line to commit messages and PR descriptions ### Custom Providers diff --git a/internal/agent/common_test.go b/internal/agent/common_test.go index b668b5dba6d42f24a4ca2eea788f7c8a608452f8..6d326ed0e5e3af195fbcbfa16c4165d2156dfc78 100644 --- a/internal/agent/common_test.go +++ b/internal/agent/common_test.go @@ -176,7 +176,7 @@ func coderAgent(r *recorder.Recorder, env fakeEnv, large, small fantasy.Language return nil, err } allTools := []fantasy.AgentTool{ - tools.NewBashTool(env.permissions, env.workingDir, cfg.Options.Attribution), + tools.NewBashTool(env.permissions, env.workingDir, cfg.Options.Attribution, large.Model()), tools.NewDownloadTool(env.permissions, env.workingDir, r.GetDefaultClient()), tools.NewEditTool(env.lspClients, env.permissions, env.history, env.workingDir), tools.NewMultiEditTool(env.lspClients, env.permissions, env.history, env.workingDir), diff --git a/internal/agent/coordinator.go b/internal/agent/coordinator.go index 907ec333fb4289bbdf928f5c1eb29d55fe8b1cc5..f313a6e397177e0553da69869156dea6e6bd43f8 100644 --- a/internal/agent/coordinator.go +++ b/internal/agent/coordinator.go @@ -327,8 +327,14 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan allTools = append(allTools, agenticFetchTool) } + // Get the model name for the agent + modelName := "" + if modelCfg, ok := c.cfg.Models[agent.Model]; ok { + modelName = modelCfg.Model + } + allTools = append(allTools, - tools.NewBashTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Options.Attribution), + tools.NewBashTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Options.Attribution, modelName), tools.NewJobOutputTool(), tools.NewJobKillTool(), tools.NewDownloadTool(c.permissions, c.cfg.WorkingDir(), nil), diff --git a/internal/agent/tools/bash.go b/internal/agent/tools/bash.go index 39f24c06cff17d9bfe2ba071dbb458c4d7eca848..c3f0bc8cd24a6c4ff7c6f775e357c90b3dc99802 100644 --- a/internal/agent/tools/bash.go +++ b/internal/agent/tools/bash.go @@ -63,6 +63,7 @@ type bashDescriptionData struct { BannedCommands string MaxOutputLength int Attribution config.Attribution + ModelName string } var bannedCommands = []string{ @@ -138,13 +139,14 @@ var bannedCommands = []string{ "ufw", } -func bashDescription(attribution *config.Attribution) string { +func bashDescription(attribution *config.Attribution, modelName string) string { bannedCommandsStr := strings.Join(bannedCommands, ", ") var out bytes.Buffer if err := bashDescriptionTpl.Execute(&out, bashDescriptionData{ BannedCommands: bannedCommandsStr, MaxOutputLength: MaxOutputLength, Attribution: *attribution, + ModelName: modelName, }); err != nil { // this should never happen. panic("failed to execute bash description template: " + err.Error()) @@ -184,10 +186,10 @@ func blockFuncs() []shell.BlockFunc { } } -func NewBashTool(permissions permission.Service, workingDir string, attribution *config.Attribution) fantasy.AgentTool { +func NewBashTool(permissions permission.Service, workingDir string, attribution *config.Attribution, modelName string) fantasy.AgentTool { return fantasy.NewAgentTool( BashToolName, - string(bashDescription(attribution)), + string(bashDescription(attribution, modelName)), func(ctx context.Context, params BashParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) { if params.Command == "" { return fantasy.NewTextErrorResponse("missing command"), nil diff --git a/internal/agent/tools/bash.tpl b/internal/agent/tools/bash.tpl index a1a5053f11765d9c228bb2fc036f8fb707ed8658..247a0a113fe0306dd63bec7bf82b610721d59fd6 100644 --- a/internal/agent/tools/bash.tpl +++ b/internal/agent/tools/bash.tpl @@ -66,7 +66,9 @@ When user asks to create git commit: {{ if .Attribution.GeneratedWith}} 💘 Generated with Crush {{ end }} -{{ if .Attribution.CoAuthoredBy}} +{{ if eq .Attribution.TrailerStyle "assisted-by"}} + Assisted-by: {{ .ModelName }} via Crush +{{ else if eq .Attribution.TrailerStyle "co-authored-by"}} Co-Authored-By: Crush {{ end }} EOF diff --git a/internal/config/config.go b/internal/config/config.go index 381a7ab384f3f78fbd2504db60b51b01e216c7db..b88ff26c9d82c9b00f4cfaa0716ed7b6881233ef 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -166,9 +166,17 @@ type Permissions struct { SkipRequests bool `json:"-"` // Automatically accept all permissions (YOLO mode) } +type TrailerStyle string + +const ( + TrailerStyleNone TrailerStyle = "none" + TrailerStyleCoAuthoredBy TrailerStyle = "co-authored-by" + TrailerStyleAssistedBy TrailerStyle = "assisted-by" +) + type Attribution struct { - CoAuthoredBy bool `json:"co_authored_by,omitempty" jsonschema:"description=Add Co-Authored-By trailer to commit messages,default=true"` - GeneratedWith bool `json:"generated_with,omitempty" jsonschema:"description=Add Generated with Crush line to commit messages and issues and PRs,default=true"` + TrailerStyle TrailerStyle `json:"trailer_style,omitempty" jsonschema:"description=Style of attribution trailer to add to commits,enum=none,enum=co-authored-by,enum=assisted-by,default=co-authored-by"` + GeneratedWith bool `json:"generated_with,omitempty" jsonschema:"description=Add Generated with Crush line to commit messages and issues and PRs,default=true"` } type Options struct { diff --git a/internal/config/load.go b/internal/config/load.go index 7a7d3ae4da2d8970954461d4a1dc9b52a544636a..80eb031218ceb52a36adaf258c3974f84a0b4db9 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -353,7 +353,7 @@ func (c *Config) setDefaults(workingDir, dataDir string) { if c.Options.Attribution == nil { c.Options.Attribution = &Attribution{ - CoAuthoredBy: true, + TrailerStyle: TrailerStyleCoAuthoredBy, GeneratedWith: true, } } diff --git a/schema.json b/schema.json index 7c11d60755bc1cf0523e458de571ea3618e0be57..3a86224fe809816550e61c17b28ded9932656699 100644 --- a/schema.json +++ b/schema.json @@ -5,10 +5,15 @@ "$defs": { "Attribution": { "properties": { - "co_authored_by": { - "type": "boolean", - "description": "Add Co-Authored-By trailer to commit messages", - "default": true + "trailer_style": { + "type": "string", + "enum": [ + "none", + "co-authored-by", + "assisted-by" + ], + "description": "Style of attribution trailer to add to commits", + "default": "co-authored-by" }, "generated_with": { "type": "boolean",