Merge PR #1452
This commit is contained in:
commit
8268840d51
5 changed files with 73 additions and 8 deletions
|
|
@ -458,7 +458,7 @@ func (cb *ContextBuilder) LoadBootstrapFiles() string {
|
|||
//
|
||||
// See: https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
|
||||
// See: https://platform.openai.com/docs/guides/prompt-caching
|
||||
func (cb *ContextBuilder) buildDynamicContext(channel, chatID string) string {
|
||||
func (cb *ContextBuilder) buildDynamicContext(channel, chatID, chatName string) string {
|
||||
now := time.Now().Format("2006-01-02 15:04 (Monday)")
|
||||
rt := fmt.Sprintf("%s %s, Go %s", runtime.GOOS, runtime.GOARCH, runtime.Version())
|
||||
|
||||
|
|
@ -466,7 +466,11 @@ func (cb *ContextBuilder) buildDynamicContext(channel, chatID string) string {
|
|||
fmt.Fprintf(&sb, "## Current Time\n%s\n\n## Runtime\n%s", now, rt)
|
||||
|
||||
if channel != "" && chatID != "" {
|
||||
fmt.Fprintf(&sb, "\n\n## Current Session\nChannel: %s\nChat ID: %s", channel, chatID)
|
||||
if chatName != "" {
|
||||
fmt.Fprintf(&sb, "\n\n## Current Session\nChannel: %s\nChat ID: %s (%s)", channel, chatID, chatName)
|
||||
} else {
|
||||
fmt.Fprintf(&sb, "\n\n## Current Session\nChannel: %s\nChat ID: %s", channel, chatID)
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
|
|
@ -477,7 +481,7 @@ func (cb *ContextBuilder) BuildMessages(
|
|||
summary string,
|
||||
currentMessage string,
|
||||
media []string,
|
||||
channel, chatID string,
|
||||
channel, chatID, chatName string,
|
||||
) []providers.Message {
|
||||
messages := []providers.Message{}
|
||||
|
||||
|
|
@ -493,7 +497,7 @@ func (cb *ContextBuilder) BuildMessages(
|
|||
staticPrompt := cb.BuildSystemPromptWithCache()
|
||||
|
||||
// Build short dynamic context (time, runtime, session) — changes per request
|
||||
dynamicCtx := cb.buildDynamicContext(channel, chatID)
|
||||
dynamicCtx := cb.buildDynamicContext(channel, chatID, chatName)
|
||||
|
||||
// Compose a single system message: static (cached) + dynamic + optional summary.
|
||||
// Keeping all system content in one message ensures every provider adapter can
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ func TestSingleSystemMessage(t *testing.T) {
|
|||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
msgs := cb.BuildMessages(tt.history, tt.summary, tt.message, nil, "test", "chat1")
|
||||
msgs := cb.BuildMessages(tt.history, tt.summary, tt.message, nil, "test", "chat1", "")
|
||||
|
||||
systemCount := 0
|
||||
for _, m := range msgs {
|
||||
|
|
@ -576,7 +576,7 @@ func TestConcurrentBuildSystemPromptWithCache(t *testing.T) {
|
|||
}
|
||||
|
||||
// Also exercise BuildMessages concurrently
|
||||
msgs := cb.BuildMessages(nil, "", "hello", nil, "test", "chat")
|
||||
msgs := cb.BuildMessages(nil, "", "hello", nil, "test", "chat", "")
|
||||
if len(msgs) < 2 {
|
||||
errs <- "BuildMessages returned fewer than 2 messages"
|
||||
return
|
||||
|
|
@ -664,6 +664,22 @@ func BenchmarkBuildMessagesWithCache(b *testing.B) {
|
|||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = cb.BuildMessages(history, "summary", "new message", nil, "cli", "test")
|
||||
_ = cb.BuildMessages(history, "summary", "new message", nil, "cli", "test", "")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildMessages_IncludesChatNameInDynamicContext(t *testing.T) {
|
||||
tmpDir := setupWorkspace(t, map[string]string{
|
||||
"IDENTITY.md": "# Identity\nTest agent.",
|
||||
})
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
cb := NewContextBuilder(tmpDir)
|
||||
msgs := cb.BuildMessages(nil, "", "hello", nil, "discord", "1234567890", "#general")
|
||||
if len(msgs) == 0 {
|
||||
t.Fatal("expected messages")
|
||||
}
|
||||
if !strings.Contains(msgs[0].Content, "Chat ID: 1234567890 (#general)") {
|
||||
t.Fatalf("dynamic context missing chat name: %q", msgs[0].Content)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ type processOptions struct {
|
|||
SessionKey string // Session identifier for history/context
|
||||
Channel string // Target channel for tool execution
|
||||
ChatID string // Target chat ID for tool execution
|
||||
ChatName string // Optional human-readable chat/channel name
|
||||
UserMessage string // User message content (may include prefix)
|
||||
Media []string // media:// refs from inbound message
|
||||
DefaultResponse string // Response when LLM returns empty
|
||||
|
|
@ -70,6 +71,7 @@ const (
|
|||
defaultResponse = "I've completed processing but have no response to give. Increase `max_tool_iterations` in config.json."
|
||||
sessionKeyAgentPrefix = "agent:"
|
||||
metadataKeyAccountID = "account_id"
|
||||
metadataKeyChatName = "chat_name"
|
||||
metadataKeyGuildID = "guild_id"
|
||||
metadataKeyTeamID = "team_id"
|
||||
metadataKeyParentPeerKind = "parent_peer_kind"
|
||||
|
|
@ -735,6 +737,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
|||
SessionKey: sessionKey,
|
||||
Channel: msg.Channel,
|
||||
ChatID: msg.ChatID,
|
||||
ChatName: inboundMetadata(msg, metadataKeyChatName),
|
||||
UserMessage: msg.Content,
|
||||
Media: msg.Media,
|
||||
DefaultResponse: defaultResponse,
|
||||
|
|
@ -879,6 +882,7 @@ func (al *AgentLoop) runAgentLoop(
|
|||
opts.Media,
|
||||
opts.Channel,
|
||||
opts.ChatID,
|
||||
opts.ChatName,
|
||||
)
|
||||
|
||||
// Resolve media:// refs: images→base64 data URLs, non-images→local paths in content
|
||||
|
|
@ -1150,7 +1154,7 @@ func (al *AgentLoop) runLLMIteration(
|
|||
newSummary := agent.Sessions.GetSummary(opts.SessionKey)
|
||||
messages = agent.ContextBuilder.BuildMessages(
|
||||
newHistory, newSummary, "",
|
||||
nil, opts.Channel, opts.ChatID,
|
||||
nil, opts.Channel, opts.ChatID, opts.ChatName,
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -459,10 +459,28 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
|
|||
"channel_id": m.ChannelID,
|
||||
"is_dm": fmt.Sprintf("%t", m.GuildID == ""),
|
||||
}
|
||||
if channelName := c.resolveChannelName(m.ChannelID); channelName != "" {
|
||||
metadata["chat_name"] = channelName
|
||||
}
|
||||
|
||||
c.HandleMessage(c.ctx, peer, m.ID, senderID, m.ChannelID, content, mediaPaths, metadata, sender)
|
||||
}
|
||||
|
||||
func (c *DiscordChannel) resolveChannelName(channelID string) string {
|
||||
if c == nil || c.session == nil || strings.TrimSpace(channelID) == "" {
|
||||
return ""
|
||||
}
|
||||
if c.session.State != nil {
|
||||
if ch, err := c.session.State.Channel(channelID); err == nil && ch != nil && strings.TrimSpace(ch.Name) != "" {
|
||||
return strings.TrimSpace(ch.Name)
|
||||
}
|
||||
}
|
||||
if ch, err := c.session.Channel(channelID); err == nil && ch != nil && strings.TrimSpace(ch.Name) != "" {
|
||||
return strings.TrimSpace(ch.Name)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// startTyping starts a continuous typing indicator loop for the given chatID.
|
||||
// It stops any existing typing loop for that chatID before starting a new one.
|
||||
func (c *DiscordChannel) startTyping(chatID string) {
|
||||
|
|
|
|||
|
|
@ -89,3 +89,26 @@ func TestApplyDiscordProxy_InvalidProxyURL(t *testing.T) {
|
|||
t.Fatal("applyDiscordProxy() expected error for invalid proxy URL, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveChannelName_FromState(t *testing.T) {
|
||||
session, err := discordgo.New("Bot test-token")
|
||||
if err != nil {
|
||||
t.Fatalf("discordgo.New() error: %v", err)
|
||||
}
|
||||
session.State = discordgo.NewState()
|
||||
if err := session.State.GuildAdd(&discordgo.Guild{ID: "guild-1"}); err != nil {
|
||||
t.Fatalf("GuildAdd() error: %v", err)
|
||||
}
|
||||
if err := session.State.ChannelAdd(&discordgo.Channel{
|
||||
ID: "123",
|
||||
GuildID: "guild-1",
|
||||
Name: "general",
|
||||
}); err != nil {
|
||||
t.Fatalf("ChannelAdd() error: %v", err)
|
||||
}
|
||||
|
||||
ch := &DiscordChannel{session: session}
|
||||
if got := ch.resolveChannelName("123"); got != "general" {
|
||||
t.Fatalf("resolveChannelName() = %q, want %q", got, "general")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue