Skip to content

Commit f89c967

Browse files
committed
sync sendmessage function
1 parent 536e26a commit f89c967

3 files changed

Lines changed: 223 additions & 12 deletions

File tree

pkg/agent/loop.go

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -466,9 +466,9 @@ func (al *AgentLoop) transcribeAudioInMessage(ctx context.Context, msg bus.Inbou
466466
}
467467

468468
// sendTranscriptionFeedback sends feedback to the user with the result of
469-
// audio transcription if the option is enabled. It sends the message directly
470-
// through the channel (bypassing the bus queue) so that ordering with the
471-
// subsequent placeholder is guaranteed.
469+
// audio transcription if the option is enabled. It uses Manager.SendMessage
470+
// which executes synchronously (rate limiting, splitting, retry) so that
471+
// ordering with the subsequent placeholder is guaranteed.
472472
func (al *AgentLoop) sendTranscriptionFeedback(
473473
ctx context.Context,
474474
channel, chatID, messageID string,
@@ -495,15 +495,7 @@ func (al *AgentLoop) sendTranscriptionFeedback(
495495
feedbackMsg = "No voice detected in the audio"
496496
}
497497

498-
ch, ok := al.channelManager.GetChannel(channel)
499-
if !ok {
500-
return
501-
}
502-
503-
sendCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
504-
defer cancel()
505-
506-
err := ch.Send(sendCtx, bus.OutboundMessage{
498+
err := al.channelManager.SendMessage(ctx, bus.OutboundMessage{
507499
Channel: channel,
508500
ChatID: chatID,
509501
Content: feedbackMsg,

pkg/channels/manager.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,39 @@ func (m *Manager) UnregisterChannel(name string) {
825825
delete(m.channels, name)
826826
}
827827

828+
// SendMessage sends an outbound message synchronously through the channel
829+
// worker's rate limiter and retry logic. It blocks until the message is
830+
// delivered (or all retries are exhausted), which preserves ordering when
831+
// a subsequent operation depends on the message having been sent.
832+
func (m *Manager) SendMessage(ctx context.Context, msg bus.OutboundMessage) error {
833+
m.mu.RLock()
834+
_, exists := m.channels[msg.Channel]
835+
w, wExists := m.workers[msg.Channel]
836+
m.mu.RUnlock()
837+
838+
if !exists {
839+
return fmt.Errorf("channel %s not found", msg.Channel)
840+
}
841+
if !wExists || w == nil {
842+
return fmt.Errorf("channel %s has no active worker", msg.Channel)
843+
}
844+
845+
maxLen := 0
846+
if mlp, ok := w.ch.(MessageLengthProvider); ok {
847+
maxLen = mlp.MaxMessageLength()
848+
}
849+
if maxLen > 0 && len([]rune(msg.Content)) > maxLen {
850+
for _, chunk := range SplitMessage(msg.Content, maxLen) {
851+
chunkMsg := msg
852+
chunkMsg.Content = chunk
853+
m.sendWithRetry(ctx, msg.Channel, w, chunkMsg)
854+
}
855+
} else {
856+
m.sendWithRetry(ctx, msg.Channel, w, msg)
857+
}
858+
return nil
859+
}
860+
828861
func (m *Manager) SendToChannel(ctx context.Context, channelName, chatID, content string) error {
829862
m.mu.RLock()
830863
_, exists := m.channels[channelName]

pkg/channels/manager_test.go

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,6 +937,192 @@ func TestManager_PlaceholderConsumedByResponse(t *testing.T) {
937937
}
938938
}
939939

940+
func TestSendMessage_Synchronous(t *testing.T) {
941+
m := newTestManager()
942+
943+
var received []bus.OutboundMessage
944+
ch := &mockChannel{
945+
sendFn: func(_ context.Context, msg bus.OutboundMessage) error {
946+
received = append(received, msg)
947+
return nil
948+
},
949+
}
950+
951+
w := &channelWorker{
952+
ch: ch,
953+
limiter: rate.NewLimiter(rate.Inf, 1),
954+
}
955+
m.channels["test"] = ch
956+
m.workers["test"] = w
957+
958+
msg := bus.OutboundMessage{
959+
Channel: "test",
960+
ChatID: "123",
961+
Content: "hello world",
962+
ReplyToMessageID: "msg-456",
963+
}
964+
965+
err := m.SendMessage(context.Background(), msg)
966+
if err != nil {
967+
t.Fatalf("expected no error, got %v", err)
968+
}
969+
970+
// SendMessage is synchronous — message should already be delivered
971+
if len(received) != 1 {
972+
t.Fatalf("expected 1 message sent, got %d", len(received))
973+
}
974+
if received[0].ReplyToMessageID != "msg-456" {
975+
t.Fatalf("expected ReplyToMessageID msg-456, got %s", received[0].ReplyToMessageID)
976+
}
977+
if received[0].Content != "hello world" {
978+
t.Fatalf("expected content 'hello world', got %s", received[0].Content)
979+
}
980+
}
981+
982+
func TestSendMessage_UnknownChannel(t *testing.T) {
983+
m := newTestManager()
984+
985+
msg := bus.OutboundMessage{
986+
Channel: "nonexistent",
987+
ChatID: "123",
988+
Content: "hello",
989+
}
990+
991+
err := m.SendMessage(context.Background(), msg)
992+
if err == nil {
993+
t.Fatal("expected error for unknown channel")
994+
}
995+
}
996+
997+
func TestSendMessage_NoWorker(t *testing.T) {
998+
m := newTestManager()
999+
1000+
ch := &mockChannel{
1001+
sendFn: func(_ context.Context, _ bus.OutboundMessage) error { return nil },
1002+
}
1003+
m.channels["test"] = ch
1004+
// No worker registered
1005+
1006+
msg := bus.OutboundMessage{
1007+
Channel: "test",
1008+
ChatID: "123",
1009+
Content: "hello",
1010+
}
1011+
1012+
err := m.SendMessage(context.Background(), msg)
1013+
if err == nil {
1014+
t.Fatal("expected error when no worker exists")
1015+
}
1016+
}
1017+
1018+
func TestSendMessage_WithRetry(t *testing.T) {
1019+
m := newTestManager()
1020+
1021+
var callCount int
1022+
ch := &mockChannel{
1023+
sendFn: func(_ context.Context, _ bus.OutboundMessage) error {
1024+
callCount++
1025+
if callCount == 1 {
1026+
return fmt.Errorf("transient: %w", ErrTemporary)
1027+
}
1028+
return nil
1029+
},
1030+
}
1031+
1032+
w := &channelWorker{
1033+
ch: ch,
1034+
limiter: rate.NewLimiter(rate.Inf, 1),
1035+
}
1036+
m.channels["test"] = ch
1037+
m.workers["test"] = w
1038+
1039+
msg := bus.OutboundMessage{
1040+
Channel: "test",
1041+
ChatID: "123",
1042+
Content: "retry me",
1043+
}
1044+
1045+
err := m.SendMessage(context.Background(), msg)
1046+
if err != nil {
1047+
t.Fatalf("expected no error, got %v", err)
1048+
}
1049+
1050+
if callCount != 2 {
1051+
t.Fatalf("expected 2 Send calls (1 failure + 1 success), got %d", callCount)
1052+
}
1053+
}
1054+
1055+
func TestSendMessage_WithSplitting(t *testing.T) {
1056+
m := newTestManager()
1057+
1058+
var received []string
1059+
ch := &mockChannelWithLength{
1060+
mockChannel: mockChannel{
1061+
sendFn: func(_ context.Context, msg bus.OutboundMessage) error {
1062+
received = append(received, msg.Content)
1063+
return nil
1064+
},
1065+
},
1066+
maxLen: 5,
1067+
}
1068+
1069+
w := &channelWorker{
1070+
ch: ch,
1071+
limiter: rate.NewLimiter(rate.Inf, 1),
1072+
}
1073+
m.channels["test"] = ch
1074+
m.workers["test"] = w
1075+
1076+
msg := bus.OutboundMessage{
1077+
Channel: "test",
1078+
ChatID: "123",
1079+
Content: "hello world",
1080+
}
1081+
1082+
err := m.SendMessage(context.Background(), msg)
1083+
if err != nil {
1084+
t.Fatalf("expected no error, got %v", err)
1085+
}
1086+
1087+
if len(received) < 2 {
1088+
t.Fatalf("expected message to be split into at least 2 chunks, got %d", len(received))
1089+
}
1090+
}
1091+
1092+
func TestSendMessage_PreservesOrdering(t *testing.T) {
1093+
m := newTestManager()
1094+
1095+
var order []string
1096+
ch := &mockChannel{
1097+
sendFn: func(_ context.Context, msg bus.OutboundMessage) error {
1098+
order = append(order, msg.Content)
1099+
return nil
1100+
},
1101+
}
1102+
1103+
w := &channelWorker{
1104+
ch: ch,
1105+
limiter: rate.NewLimiter(rate.Inf, 1),
1106+
}
1107+
m.channels["test"] = ch
1108+
m.workers["test"] = w
1109+
1110+
// Send two messages sequentially — they must arrive in order
1111+
_ = m.SendMessage(context.Background(), bus.OutboundMessage{
1112+
Channel: "test", ChatID: "1", Content: "first",
1113+
})
1114+
_ = m.SendMessage(context.Background(), bus.OutboundMessage{
1115+
Channel: "test", ChatID: "1", Content: "second",
1116+
})
1117+
1118+
if len(order) != 2 {
1119+
t.Fatalf("expected 2 messages, got %d", len(order))
1120+
}
1121+
if order[0] != "first" || order[1] != "second" {
1122+
t.Fatalf("expected [first, second], got %v", order)
1123+
}
1124+
}
1125+
9401126
func TestManager_SendPlaceholder(t *testing.T) {
9411127
mgr := &Manager{
9421128
channels: make(map[string]Channel),

0 commit comments

Comments
 (0)