Skip to content

Commit fa2e603

Browse files
committed
Suppress context.Canceled logs; use SDK UI helpers
Silence noisy logging/processing for context.Canceled across AI bridge code by returning early in setModelTyping, savePortalQuiet, and StreamSession.logWarn. Replace local UI helper wrappers with SDK-provided helpers for building compact final UI messages and default final-edit extras, and update tests accordingly. Simplify agent loop finalization by removing the errorExit branching and guarding FinalizeAgentLoop to avoid double-finalization; also remove special-case notification for canceled agent loops. Add a unit test to ensure context.Canceled is suppressed in logWarn and adjust imports where needed.
1 parent e4bfdc5 commit fa2e603

13 files changed

+55
-59
lines changed

bridges/ai/agent_loop_runtime.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,13 +57,6 @@ func withActivityTimeout(parent context.Context, timeout time.Duration, timeoutE
5757
return ctx, touch, func() { cancel(context.Canceled) }
5858
}
5959

60-
func withAgentLoopActivity(ctx context.Context, touch func()) context.Context {
61-
if touch == nil {
62-
return ctx
63-
}
64-
return context.WithValue(ctx, agentLoopActivityKey{}, touch)
65-
}
66-
6760
func touchAgentLoopActivity(ctx context.Context) {
6861
if touch, ok := ctx.Value(agentLoopActivityKey{}).(func()); ok && touch != nil {
6962
touch()
@@ -83,7 +76,7 @@ func agentLoopInactivityCause(ctx context.Context) error {
8376

8477
func (oc *AIClient) withAgentLoopInactivityTimeout(ctx context.Context) (context.Context, context.CancelFunc) {
8578
runCtx, touch, cancel := withActivityTimeout(ctx, agentLoopInactivityTimeout, errAgentLoopInactivityTimeout)
86-
return withAgentLoopActivity(runCtx, touch), cancel
79+
return context.WithValue(runCtx, agentLoopActivityKey{}, touch), cancel
8780
}
8881

8982
func runAgentLoopStreamStep[T any](

bridges/ai/handleai.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ func (oc *AIClient) setModelTyping(ctx context.Context, portal *bridgev2.Portal,
177177
timeout = 0 // Zero timeout stops typing
178178
}
179179
if err := intent.MarkTyping(ctx, portal.MXID, bridgev2.TypingTypeText, timeout); err != nil {
180+
if errors.Is(err, context.Canceled) {
181+
return
182+
}
180183
oc.loggerForContext(ctx).Warn().Err(err).Bool("typing", typing).Msg("Failed to set typing indicator")
181184
}
182185
}

bridges/ai/handlematrix.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,9 @@ func (oc *AIClient) handleTextFileMessage(
929929
// savePortalQuiet saves portal and logs errors without failing
930930
func (oc *AIClient) savePortalQuiet(ctx context.Context, portal *bridgev2.Portal, action string) {
931931
if err := portal.Save(ctx); err != nil {
932+
if errors.Is(err, context.Canceled) {
933+
return
934+
}
932935
oc.loggerForContext(ctx).Warn().Err(err).Str("action", action).Msg("Failed to save portal")
933936
}
934937
}

bridges/ai/response_finalization.go

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -543,9 +543,9 @@ func (oc *AIClient) sendFinalAssistantTurnContent(ctx context.Context, portal *b
543543
}
544544
linkPreviews := generateOutboundLinkPreviews(ctx, rendered.Body, intent, portal, sourceCitations, getLinkPreviewConfig(&oc.connector.Config))
545545

546-
uiMessage := buildCompactFinalUIMessage(oc.buildStreamUIMessage(state, meta, linkPreviews))
546+
uiMessage := sdk.BuildCompactFinalUIMessage(oc.buildStreamUIMessage(state, meta, linkPreviews))
547547

548-
topLevelExtra := buildFinalEditTopLevelExtra()
548+
topLevelExtra := sdk.BuildDefaultFinalEditTopLevelExtra()
549549
if state != nil && state.turn != nil {
550550
finalTopLevelExtra := topLevelExtra
551551
if len(uiMessage) > 0 || len(linkPreviews) > 0 {
@@ -579,12 +579,6 @@ func (oc *AIClient) sendFinalAssistantTurnContent(ctx context.Context, portal *b
579579
}
580580
}
581581

582-
func buildFinalEditTopLevelExtra() map[string]any {
583-
return map[string]any{
584-
"com.beeper.dont_render_edited": true,
585-
}
586-
}
587-
588582
// generateOutboundLinkPreviews extracts URLs from AI response text, generates link previews, and uploads images to Matrix.
589583
// When citations are provided (e.g. from Exa search results), matching URLs use the citation's
590584
// image directly instead of fetching the page's HTML.

bridges/ai/response_finalization_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func TestBuildFinalEditUIMessage_IncludesSourceAndFileParts(t *testing.T) {
4646
streamui.ApplyChunk(state.turn.UIState(), map[string]any{"type": "text-delta", "id": "text-1", "delta": "hello"})
4747
streamui.ApplyChunk(state.turn.UIState(), map[string]any{"type": "text-end", "id": "text-1"})
4848

49-
ui := buildCompactFinalUIMessage(oc.buildStreamUIMessage(state, modelModeTestMeta("openai/gpt-4.1"), nil))
49+
ui := bridgesdk.BuildCompactFinalUIMessage(oc.buildStreamUIMessage(state, modelModeTestMeta("openai/gpt-4.1"), nil))
5050
if ui == nil {
5151
t.Fatalf("expected final edit UI message")
5252
}
@@ -118,7 +118,7 @@ func TestBuildFinalEditUIMessage_OmitsTextButKeepsReasoningAndToolPartsWhenTheyF
118118
streamui.ApplyChunk(state.turn.UIState(), map[string]any{"type": "reasoning-delta", "id": "reasoning-2", "delta": "thinking"})
119119
streamui.ApplyChunk(state.turn.UIState(), map[string]any{"type": "reasoning-end", "id": "reasoning-2"})
120120

121-
ui := buildCompactFinalUIMessage(oc.buildStreamUIMessage(state, modelModeTestMeta("openai/gpt-4.1"), nil))
121+
ui := bridgesdk.BuildCompactFinalUIMessage(oc.buildStreamUIMessage(state, modelModeTestMeta("openai/gpt-4.1"), nil))
122122
parts, _ := ui["parts"].([]any)
123123
foundReasoning := false
124124
foundTool := false
@@ -156,7 +156,7 @@ func TestBuildFinalEditUIMessage_UsesNestedUsageContextLimitFromSnapshot(t *test
156156
streamui.ApplyChunk(state.turn.UIState(), map[string]any{"type": "text-delta", "id": "text-usage", "delta": "hello"})
157157
streamui.ApplyChunk(state.turn.UIState(), map[string]any{"type": "text-end", "id": "text-usage"})
158158

159-
ui := buildCompactFinalUIMessage(oc.buildStreamUIMessage(state, modelModeTestMeta("openai/gpt-4.1"), nil))
159+
ui := bridgesdk.BuildCompactFinalUIMessage(oc.buildStreamUIMessage(state, modelModeTestMeta("openai/gpt-4.1"), nil))
160160
metadata, ok := ui["metadata"].(map[string]any)
161161
if !ok {
162162
t.Fatalf("expected metadata map, got %T", ui["metadata"])
@@ -198,7 +198,7 @@ func TestBuildFinalEditTopLevelExtra_KeepsOnlyEditMetadata(t *testing.T) {
198198
MatchedURL: "https://example.com",
199199
}}
200200

201-
extra := buildFinalEditTopLevelExtra()
201+
extra := bridgesdk.BuildDefaultFinalEditTopLevelExtra()
202202

203203
if _, ok := extra["body"]; ok {
204204
t.Fatalf("expected body fallback to come from Matrix edit content, got %#v", extra["body"])

bridges/ai/response_retry.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -363,12 +363,6 @@ func (oc *AIClient) runAgentLoopWithRetry(
363363
if success || err == nil {
364364
return
365365
}
366-
if errors.Is(err, context.Canceled) {
367-
if timeoutErr := agentLoopInactivityCause(ctx); timeoutErr != nil {
368-
oc.notifyMatrixSendFailure(ctx, portal, evt, timeoutErr)
369-
}
370-
return
371-
}
372366
oc.notifyMatrixSendFailure(ctx, portal, evt, err)
373367
}
374368

bridges/ai/streaming_executor.go

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ func executeAgentLoopRounds(
8484
continueLoop, cle, err := provider.RunAgentTurn(ctx, evt, round)
8585
touchAgentLoopActivity(ctx)
8686
if cle != nil || err != nil {
87-
finalizeAgentLoopExit(ctx, provider, true)
87+
finalizeAgentLoopExit(ctx, provider)
8888
return false, cle, err
8989
}
9090
if continueLoop {
@@ -93,26 +93,14 @@ func executeAgentLoopRounds(
9393

9494
// Queued user messages are dispatched after room release via processPendingQueue.
9595
// Finalize this turn immediately so later prompts cannot reopen it with more edits.
96-
finalizeAgentLoopExit(ctx, provider, false)
96+
finalizeAgentLoopExit(ctx, provider)
9797
return true, nil, nil
9898
}
9999
}
100100

101-
func finalizeAgentLoopExit(ctx context.Context, provider agentLoopProvider, errorExit bool) {
101+
func finalizeAgentLoopExit(ctx context.Context, provider agentLoopProvider) {
102102
if provider == nil {
103103
return
104104
}
105-
if errorExit {
106-
switch p := provider.(type) {
107-
case *chatCompletionsTurnAdapter:
108-
if p != nil && p.state != nil && p.state.completedAtMs != 0 {
109-
return
110-
}
111-
case *responsesTurnAdapter:
112-
if p != nil && p.state != nil && p.state.completedAtMs != 0 {
113-
return
114-
}
115-
}
116-
}
117105
provider.FinalizeAgentLoop(ctx)
118106
}

bridges/ai/streaming_responses_api.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,9 @@ func (a *responsesTurnAdapter) RunAgentTurn(
176176
}
177177

178178
func (a *responsesTurnAdapter) FinalizeAgentLoop(ctx context.Context) {
179+
if a.state == nil || a.state.completedAtMs != 0 {
180+
return
181+
}
179182
a.oc.finalizeResponsesStream(ctx, a.log, a.portal, a.state, a.meta)
180183
}
181184

bridges/ai/streaming_ui_helpers.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,6 @@ func (oc *AIClient) buildStreamUIMessage(state *streamingState, meta *PortalMeta
6767
return sdk.UIMessageFromTurnData(turnData)
6868
}
6969

70-
func buildCompactFinalUIMessage(uiMessage map[string]any) map[string]any {
71-
return sdk.BuildCompactFinalUIMessage(uiMessage)
72-
}
73-
7470
func shouldContinueChatToolLoop(finishReason string, toolCallCount int) bool {
7571
if toolCallCount <= 0 {
7672
return false

sdk/final_edit.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ type FinalEditFitDetails struct {
150150
}
151151

152152
func (d FinalEditFitDetails) Changed() bool {
153-
return d.ClearedFormattedBody || d.DroppedLinkPreviews || d.CompactedUIMessage || d.DroppedUIMessage || d.DroppedExtra || d.TrimmedBody
153+
return d.Summary() != ""
154154
}
155155

156156
func (d FinalEditFitDetails) Summary() string {
@@ -228,18 +228,16 @@ func estimateFinalEditContentSize(payload *FinalEditPayload, target id.EventID)
228228
}
229229

230230
func FitFinalEditPayload(payload *FinalEditPayload, target id.EventID) (*FinalEditPayload, FinalEditFitDetails, error) {
231-
fitted := cloneFinalEditPayload(payload)
232-
if fitted == nil || fitted.Content == nil {
233-
return fitted, FinalEditFitDetails{}, nil
234-
}
235-
details := FinalEditFitDetails{
236-
OriginalSize: estimateFinalEditContentSize(fitted, target),
231+
if payload == nil || payload.Content == nil {
232+
return payload, FinalEditFitDetails{}, nil
237233
}
238-
size := details.OriginalSize
239-
if size <= MaxMatrixEventContentBytes {
240-
details.FinalSize = size
241-
return fitted, details, nil
234+
initialSize := estimateFinalEditContentSize(payload, target)
235+
if initialSize <= MaxMatrixEventContentBytes {
236+
return payload, FinalEditFitDetails{OriginalSize: initialSize, FinalSize: initialSize}, nil
242237
}
238+
fitted := cloneFinalEditPayload(payload)
239+
details := FinalEditFitDetails{OriginalSize: initialSize}
240+
size := initialSize
243241

244242
if fitted.Content.Format != "" || fitted.Content.FormattedBody != "" {
245243
fitted.Content.Format = ""
@@ -283,6 +281,7 @@ func FitFinalEditPayload(payload *FinalEditPayload, target id.EventID) (*FinalEd
283281
if size > MaxMatrixEventContentBytes && fitted.Content != nil && fitted.Content.Body != "" {
284282
originalBody := fitted.Content.Body
285283
best := strings.TrimSpace(originalBody)
284+
bestSize := size
286285
low, high := 1, len(originalBody)
287286
for low <= high {
288287
mid := (low + high) / 2
@@ -296,14 +295,15 @@ func FitFinalEditPayload(payload *FinalEditPayload, target id.EventID) (*FinalEd
296295
candidateSize := estimateFinalEditContentSize(fitted, target)
297296
if candidateSize <= MaxMatrixEventContentBytes {
298297
best = candidate
298+
bestSize = candidateSize
299299
low = mid + 1
300300
} else {
301301
high = mid - 1
302302
}
303303
}
304304
fitted.Content.Body = best
305305
details.TrimmedBody = best != strings.TrimSpace(originalBody)
306-
size = estimateFinalEditContentSize(fitted, target)
306+
size = bestSize
307307
}
308308
details.FinalSize = size
309309
if size > MaxMatrixEventContentBytes {

0 commit comments

Comments
 (0)