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://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
|
||||||
// See: https://platform.openai.com/docs/guides/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)")
|
now := time.Now().Format("2006-01-02 15:04 (Monday)")
|
||||||
rt := fmt.Sprintf("%s %s, Go %s", runtime.GOOS, runtime.GOARCH, runtime.Version())
|
rt := fmt.Sprintf("%s %s, Go %s", runtime.GOOS, runtime.GOARCH, runtime.Version())
|
||||||
|
|
||||||
|
|
@ -466,8 +466,12 @@ func (cb *ContextBuilder) buildDynamicContext(channel, chatID string) string {
|
||||||
fmt.Fprintf(&sb, "## Current Time\n%s\n\n## Runtime\n%s", now, rt)
|
fmt.Fprintf(&sb, "## Current Time\n%s\n\n## Runtime\n%s", now, rt)
|
||||||
|
|
||||||
if channel != "" && chatID != "" {
|
if 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)
|
fmt.Fprintf(&sb, "\n\n## Current Session\nChannel: %s\nChat ID: %s", channel, chatID)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
@ -477,7 +481,7 @@ func (cb *ContextBuilder) BuildMessages(
|
||||||
summary string,
|
summary string,
|
||||||
currentMessage string,
|
currentMessage string,
|
||||||
media []string,
|
media []string,
|
||||||
channel, chatID string,
|
channel, chatID, chatName string,
|
||||||
) []providers.Message {
|
) []providers.Message {
|
||||||
messages := []providers.Message{}
|
messages := []providers.Message{}
|
||||||
|
|
||||||
|
|
@ -493,7 +497,7 @@ func (cb *ContextBuilder) BuildMessages(
|
||||||
staticPrompt := cb.BuildSystemPromptWithCache()
|
staticPrompt := cb.BuildSystemPromptWithCache()
|
||||||
|
|
||||||
// Build short dynamic context (time, runtime, session) — changes per request
|
// 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.
|
// Compose a single system message: static (cached) + dynamic + optional summary.
|
||||||
// Keeping all system content in one message ensures every provider adapter can
|
// 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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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
|
systemCount := 0
|
||||||
for _, m := range msgs {
|
for _, m := range msgs {
|
||||||
|
|
@ -576,7 +576,7 @@ func TestConcurrentBuildSystemPromptWithCache(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also exercise BuildMessages concurrently
|
// Also exercise BuildMessages concurrently
|
||||||
msgs := cb.BuildMessages(nil, "", "hello", nil, "test", "chat")
|
msgs := cb.BuildMessages(nil, "", "hello", nil, "test", "chat", "")
|
||||||
if len(msgs) < 2 {
|
if len(msgs) < 2 {
|
||||||
errs <- "BuildMessages returned fewer than 2 messages"
|
errs <- "BuildMessages returned fewer than 2 messages"
|
||||||
return
|
return
|
||||||
|
|
@ -664,6 +664,22 @@ func BenchmarkBuildMessagesWithCache(b *testing.B) {
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
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
|
SessionKey string // Session identifier for history/context
|
||||||
Channel string // Target channel for tool execution
|
Channel string // Target channel for tool execution
|
||||||
ChatID string // Target chat ID 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)
|
UserMessage string // User message content (may include prefix)
|
||||||
Media []string // media:// refs from inbound message
|
Media []string // media:// refs from inbound message
|
||||||
DefaultResponse string // Response when LLM returns empty
|
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."
|
defaultResponse = "I've completed processing but have no response to give. Increase `max_tool_iterations` in config.json."
|
||||||
sessionKeyAgentPrefix = "agent:"
|
sessionKeyAgentPrefix = "agent:"
|
||||||
metadataKeyAccountID = "account_id"
|
metadataKeyAccountID = "account_id"
|
||||||
|
metadataKeyChatName = "chat_name"
|
||||||
metadataKeyGuildID = "guild_id"
|
metadataKeyGuildID = "guild_id"
|
||||||
metadataKeyTeamID = "team_id"
|
metadataKeyTeamID = "team_id"
|
||||||
metadataKeyParentPeerKind = "parent_peer_kind"
|
metadataKeyParentPeerKind = "parent_peer_kind"
|
||||||
|
|
@ -735,6 +737,7 @@ func (al *AgentLoop) processMessage(ctx context.Context, msg bus.InboundMessage)
|
||||||
SessionKey: sessionKey,
|
SessionKey: sessionKey,
|
||||||
Channel: msg.Channel,
|
Channel: msg.Channel,
|
||||||
ChatID: msg.ChatID,
|
ChatID: msg.ChatID,
|
||||||
|
ChatName: inboundMetadata(msg, metadataKeyChatName),
|
||||||
UserMessage: msg.Content,
|
UserMessage: msg.Content,
|
||||||
Media: msg.Media,
|
Media: msg.Media,
|
||||||
DefaultResponse: defaultResponse,
|
DefaultResponse: defaultResponse,
|
||||||
|
|
@ -879,6 +882,7 @@ func (al *AgentLoop) runAgentLoop(
|
||||||
opts.Media,
|
opts.Media,
|
||||||
opts.Channel,
|
opts.Channel,
|
||||||
opts.ChatID,
|
opts.ChatID,
|
||||||
|
opts.ChatName,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Resolve media:// refs: images→base64 data URLs, non-images→local paths in content
|
// 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)
|
newSummary := agent.Sessions.GetSummary(opts.SessionKey)
|
||||||
messages = agent.ContextBuilder.BuildMessages(
|
messages = agent.ContextBuilder.BuildMessages(
|
||||||
newHistory, newSummary, "",
|
newHistory, newSummary, "",
|
||||||
nil, opts.Channel, opts.ChatID,
|
nil, opts.Channel, opts.ChatID, opts.ChatName,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -459,10 +459,28 @@ func (c *DiscordChannel) handleMessage(s *discordgo.Session, m *discordgo.Messag
|
||||||
"channel_id": m.ChannelID,
|
"channel_id": m.ChannelID,
|
||||||
"is_dm": fmt.Sprintf("%t", m.GuildID == ""),
|
"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)
|
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.
|
// 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.
|
// It stops any existing typing loop for that chatID before starting a new one.
|
||||||
func (c *DiscordChannel) startTyping(chatID string) {
|
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")
|
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