Skip to content

Commit 068a445

Browse files
authored
feat: add configurable mention triggers support (#334)
- Add mention configuration to config structure with triggers array and default trigger - Implement enhanced mention parsing with regex-based exact matching - Support multiple mention triggers (@ai, @code-assistant, @qiniu-ci) - Add comprehensive test coverage for mention parsing functionality - Maintain backward compatibility with existing @qiniu-ci trigger
1 parent df9faf8 commit 068a445

File tree

4 files changed

+371
-23
lines changed

4 files changed

+371
-23
lines changed

config.example.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,13 @@ docker:
3434
# Code provider configuration
3535
code_provider: claude # Options: claude, gemini
3636
use_docker: true # Whether to use Docker, false means use local CLI
37+
38+
# AI mention configuration
39+
mention:
40+
# List of mention triggers that will activate AI processing
41+
# Supports multiple triggers for different use cases
42+
triggers:
43+
- "@qiniu-ci" # Default company AI assistant
44+
- "@qiniu-ai" # another company AI assistant
45+
# Default trigger (used when no specific trigger list is provided)
46+
default_trigger: "@qiniu-ci"

internal/config/config.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ type Config struct {
2222

2323
// v0.6 Configuration
2424
Commands CommandsConfig `yaml:"commands"`
25+
// AI Mention Configuration
26+
Mention MentionConfig `yaml:"mention"`
2527
}
2628

2729
type GeminiConfig struct {
@@ -73,6 +75,13 @@ type CommandsConfig struct {
7375
GlobalPath string `yaml:"global_path"`
7476
}
7577

78+
type MentionConfig struct {
79+
// 可配置的mention目标,支持多个
80+
Triggers []string `yaml:"triggers"`
81+
// 默认的mention目标(向后兼容)
82+
DefaultTrigger string `yaml:"default_trigger"`
83+
}
84+
7685
func Load(configPath string) (*Config, error) {
7786
// 首先尝试从文件加载
7887
if _, err := os.Stat(configPath); err == nil {
@@ -168,6 +177,10 @@ func (c *Config) loadFromEnv() {
168177
if globalPath := os.Getenv("GLOBAL_COMMANDS_PATH"); globalPath != "" {
169178
c.Commands.GlobalPath = globalPath
170179
}
180+
// Mention configuration from environment
181+
if mentionTrigger := os.Getenv("MENTION_TRIGGER"); mentionTrigger != "" {
182+
c.Mention.DefaultTrigger = mentionTrigger
183+
}
171184
}
172185

173186
func loadFromEnv() *Config {
@@ -213,6 +226,10 @@ func loadFromEnv() *Config {
213226
Commands: CommandsConfig{
214227
GlobalPath: os.Getenv("GLOBAL_COMMANDS_PATH"),
215228
},
229+
Mention: MentionConfig{
230+
Triggers: []string{getEnvOrDefault("MENTION_TRIGGER", "@qiniu-ci")},
231+
DefaultTrigger: getEnvOrDefault("MENTION_TRIGGER", "@qiniu-ci"),
232+
},
216233
CodeProvider: getEnvOrDefault("CODE_PROVIDER", "claude"),
217234
UseDocker: getEnvBoolOrDefault("USE_DOCKER", true),
218235
}

pkg/models/events.go

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package models
22

33
import (
4+
"regexp"
45
"strings"
56
"time"
67

@@ -156,8 +157,33 @@ const (
156157
AIModelGemini = "gemini"
157158
)
158159

159-
// HasCommand 检查上下文是否包含命令
160+
// MentionConfig 提及配置接口
161+
type MentionConfig interface {
162+
GetTriggers() []string
163+
GetDefaultTrigger() string
164+
}
165+
166+
// ConfigMentionAdapter 从内部config包适配到models包
167+
type ConfigMentionAdapter struct {
168+
Triggers []string
169+
DefaultTrigger string
170+
}
171+
172+
func (c *ConfigMentionAdapter) GetTriggers() []string {
173+
return c.Triggers
174+
}
175+
176+
func (c *ConfigMentionAdapter) GetDefaultTrigger() string {
177+
return c.DefaultTrigger
178+
}
179+
180+
// HasCommand 检查上下文是否包含命令(使用默认mention配置)
160181
func HasCommand(ctx GitHubContext) (*CommandInfo, bool) {
182+
return HasCommandWithConfig(ctx, nil)
183+
}
184+
185+
// HasCommandWithConfig 检查上下文是否包含命令(支持自定义mention配置)
186+
func HasCommandWithConfig(ctx GitHubContext, mentionConfig MentionConfig) (*CommandInfo, bool) {
161187
var content string
162188

163189
switch c := ctx.(type) {
@@ -186,7 +212,11 @@ func HasCommand(ctx GitHubContext) (*CommandInfo, bool) {
186212
return cmdInfo, true
187213
}
188214

189-
// Then try to parse as @claude mention
215+
// Then try to parse as mention with config
216+
if mentionConfig != nil {
217+
return parseMentionWithConfig(content, mentionConfig)
218+
}
219+
// Fallback to default mention parsing
190220
return parseMention(content)
191221
}
192222

@@ -231,43 +261,58 @@ func parseCommand(content string) (*CommandInfo, bool) {
231261
}, true
232262
}
233263

234-
// parseMention 解析@qiniu-ci提及
264+
// parseMention 解析@qiniu-ci提及(默认触发词,向后兼容)
235265
func parseMention(content string) (*CommandInfo, bool) {
236-
content = strings.TrimSpace(content)
266+
return parseMentionWithTrigger(content, CommandClaude)
267+
}
237268

238-
// 检查是否包含@qiniu-ci
239-
if !strings.Contains(content, CommandClaude) {
240-
return nil, false
269+
// parseMentionWithConfig 使用配置解析mention
270+
func parseMentionWithConfig(content string, config MentionConfig) (*CommandInfo, bool) {
271+
triggers := config.GetTriggers()
272+
if len(triggers) == 0 {
273+
triggers = []string{config.GetDefaultTrigger()}
241274
}
242275

243-
// 找到@qiniu-ci的位置
244-
mentionIndex := strings.Index(content, CommandClaude)
245-
if mentionIndex == -1 {
276+
// 尝试所有配置的触发词
277+
for _, trigger := range triggers {
278+
if trigger == "" {
279+
continue
280+
}
281+
if cmdInfo, found := parseMentionWithTrigger(content, trigger); found {
282+
return cmdInfo, true
283+
}
284+
}
285+
286+
return nil, false
287+
}
288+
289+
// parseMentionWithTrigger 使用指定触发词解析mention,传递完整评论内容
290+
func parseMentionWithTrigger(content string, trigger string) (*CommandInfo, bool) {
291+
content = strings.TrimSpace(content)
292+
pattern := `(^|\s)` + regexp.QuoteMeta(trigger) + `([\s.,!?;:]|$)`
293+
re := regexp.MustCompile(pattern)
294+
295+
match := re.FindStringSubmatch(content)
296+
if match == nil {
246297
return nil, false
247298
}
248299

249-
// 提取@qiniu-ci之后的内容作为参数
250-
afterMention := strings.TrimSpace(content[mentionIndex+len(CommandClaude):])
300+
// NOTE(CarlJin): mention 模式传递暂时完整评论内容
301+
fullContent := content
251302

252-
// 解析AI模型和参数(类似于parseCommand的逻辑)
253303
var aiModel string
254-
var args string
255304

256-
if strings.HasPrefix(afterMention, "-claude") {
305+
// 查找模型指定标志
306+
if strings.Contains(fullContent, "-claude") {
257307
aiModel = AIModelClaude
258-
args = strings.TrimSpace(strings.TrimPrefix(afterMention, "-claude"))
259-
} else if strings.HasPrefix(afterMention, "-gemini") {
308+
} else if strings.Contains(fullContent, "-gemini") {
260309
aiModel = AIModelGemini
261-
args = strings.TrimSpace(strings.TrimPrefix(afterMention, "-gemini"))
262-
} else {
263-
aiModel = ""
264-
args = afterMention
265310
}
266311

267312
return &CommandInfo{
268-
Command: CommandClaude,
313+
Command: CommandClaude, // 总是使用CommandClaude作为mention的标识
269314
AIModel: aiModel,
270-
Args: args,
315+
Args: fullContent, // 传递完整评论内容
271316
RawText: content,
272317
}, true
273318
}

0 commit comments

Comments
 (0)