Skip to content

AG-UI alignments for AI bridge#9

Open
batuhan wants to merge 7 commits into
mainfrom
batuhan/ag-ui-align
Open

AG-UI alignments for AI bridge#9
batuhan wants to merge 7 commits into
mainfrom
batuhan/ag-ui-align

Conversation

@batuhan
Copy link
Copy Markdown
Member

@batuhan batuhan commented May 31, 2026

No description provided.

batuhan added 6 commits May 28, 2026 19:55
Switch AG-UI approval tooling to use aistream types and interrupt-based outcomes. Replaced agui.ToolApproval/ToolApprovalResponse usages with aistream equivalents across runner, plans, types, and client code, and call writer.Interrupt() when approval is requested. Updated tests to expect TOOL_CALL_RESULT and RUN_FINISHED interrupts (and to check interrupt metadata/choices) instead of custom approval events; added helpers (firstInterrupts, eventInterrupts, approvalChoicesFromMetadata, stringFromAny) to normalize interrupt/choice payloads. Adjusted annotateApprovalEventIDs to annotate interrupt outcomes, and updated buildAIApprovalContinuationRunWithApprovals call sites. Also updated go.mod/go.sum (dependency bumps and local replace for ai-bridge).
Bump github.com/beeper/ai-bridge and adapt code to its updated APIs. Replace ToolApprovalRequestedWithMetadata with ToolApprovalRequestedWithRequest in ai_runner, constructing an ApprovalRequest. Expand approval context to include title/description/plan/expires/choices/metadata and add parsing helpers (mapField, approvalChoicesField). Refactor DummyClient message waiting to use aibridgev2.WaitForMessageEventID with per-receiver timeouts, remove the old lookupMessageMXID usage, and centralize approval context creation via approvalContextForPrompt. These changes align the connector with the newer ai-bridge contract and provide richer approval metadata and more robust message lookup.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Review Change Stack

Warning

Review limit reached

@batuhan, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 52 minutes and 41 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bdd085d5-5d6e-49f1-a657-ce440f860bca

📥 Commits

Reviewing files that changed from the base of the PR and between f732d6a and ae266ba.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (11)
  • go.mod
  • pkg/connector/ai_commands.go
  • pkg/connector/ai_parse_helpers.go
  • pkg/connector/ai_plans.go
  • pkg/connector/ai_runner.go
  • pkg/connector/ai_runtime_test.go
  • pkg/connector/ai_text.go
  • pkg/connector/ai_types.go
  • pkg/connector/client.go
  • pkg/connector/client_test.go
  • pkg/connector/commands.go
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch batuhan/ag-ui-align

Comment @coderabbitai help to get the list of available commands and usage tips.

@batuhan batuhan marked this pull request as ready for review May 31, 2026 20:28
@indent
Copy link
Copy Markdown

indent Bot commented May 31, 2026

PR Summary

Migrates the dummybridge AI connector from the legacy agui.ToolApproval* custom-event approach to ai-bridge's new interrupt-based approval model and generalizes the AI streaming path to work outside of portals. Bumps ai-bridge (with a local replace ../ai-bridge while iterating), switches stream packing to cadence-based PackRunByTimeFromSeq, and rebuilds the approval context parsing/timeout handling around the new aistream types.

  • Replaces agui.ToolApproval{,Response} with the aistream equivalents across client.go, runner, plans, types, and tests; runner now calls ToolApprovalRequestedWithRequest(ApprovalRequest{...}) and the planner explicitly writer.Interrupt()s on errApprovalRequested.
  • Migrates all agui.Event map access to the new struct accessors (Type(), Get(), Set(), Has()), and rewrites approval-walking helpers (annotateApprovalEventIDs, filterPendingPrompts, filterPendingInterrupts, approvalContinuationStart, toolResultApprovalID) to read interrupts from RunFinishedOutcome instead of custom events.
  • Introduces aiRunTarget so anchor/carriers/approval prompts/final edits can target either a portal or a raw bot+roomID; runStreamCommand now works outside of a portal via queueAIResponseInRoom and aimatrix.*Content helpers.
  • Adds an approval queue + 5-minute timeout (aistream.NewApprovalQueue / TimedOutApprovalResponse) with portal and room timeout schedulers; removes the queued ApprovalOptionReaction events and the corresponding isApprovalOptionReaction reaction filter.
  • Replaces the polling-based lookupMessageMXID with aibridgev2.WaitForMessageEventID and a per-receiver timeout split; deletes the local carrierTimestamp/runStartTimestamp/eventTimestamp helpers in favor of aistream.CarrierTimestamp.
  • Rewrites approvalContextFromAny/approvalContextFromMap to field-by-field decode the richer ApprovalContext (title/description/plan/expires/choices/metadata), with new stringField/intField/boolField/mapField/approvalChoicesField helpers.
  • Tightens text generation: trimText now defers to trimCompleteText (sentence-boundary only), buildCompleteLoremText pre-pads then trims, and sliceByStep walks naturalTextUnits (paragraphs + sentence splits, preserving markdown-sensitive blocks whole).
  • Normalizes stream/stream-tools --terminal enum names (tool_calls/content_filter/other) with ValidFinishReason validation, and updates go.mod/go.sum (ai-bridge bump, go.mau.fi/util to v0.9.9, golang.org/x bumps, plus a local replace github.com/beeper/ai-bridge => ../ai-bridge).
  • Rewrites the test suite to assert interrupt outcomes/TOOL_CALL_RESULT events and adds helpers (firstInterrupts, eventInterrupts, approvalChoicesFromMetadata, stringFromAny).

Issues

6 potential issues found:

  • waitForMessageMXID now iterates receivers sequentially with perReceiverTimeout = timeout / len(receivers) but floors that to 1s, so with small caller timeouts the total wait can exceed the requested budget (the original code shared a single context.WithTimeout). Not reachable today (only 10s/30s call sites), but easy to misuse next time someone calls this with < 2s. → Autofix
  • scheduleAIApprovalTimeout looks up the timed-out approval message only under portal.Receiver, but waitForMessageMXID resolves the approval event under either portal.Receiver or dc.UserLogin.ID. If the approval message lives under the alternate login (common when Receiver != UserLogin.ID), the timeout handler logs "Timed-out AI approval message was not found" and silently drops the timeout response, leaving the run paused. Iterate both receivers in lookupMessage to match the forward lookup. → Autofix
  • trimCompleteText's fallback loop is for i := min(limit+128, len(text)); i > limit; i++i starts at >= limit+1 and is incremented, so the loop walks past len(text) and text[i-1] panics with "index out of range". This will crash any stream/stream-tools invocation where the descending loop doesn't find a .!? in the last 25% of the trim window. Change i++ to i-- (or rebound the loop to walk forward from limit to len(text)). → Autofix
  • sendApprovalPrompt constructs approvalCtx via approvalContextForPrompt(...) and then, on the portal path, immediately overwrites it with the return value of dc.queueAIApprovalPrompt, which itself calls approvalContextForPrompt internally — so the local one is always thrown away. Move the local construction into the else branch (or have queueAIApprovalPrompt accept a prebuilt context). → Autofix
  • Two leftover self-assignments after removing splitCarriersForTimedEmission: initialCarriers = initialCarriers and annotatedCarriers = annotatedCarriers in TestApprovalLifecycleCarriesNoticeTargetAndContinuation. go vet's self-assignment analyzer will flag both — delete the lines. → Autofix
  • approvalContextFromAny dropped its case string: and generic json.Marshal/Unmarshal fallback, so approval metadata that arrives as a JSON string (or any non-map/RawMessage shape) inside Content.Raw will silently fail to parse and the continuation will be lost. Restore the string/JSON fallback so future producers that re-serialize the field keep working. → Autofix
1 issue already resolved
  • go.mod adds replace github.com/beeper/ai-bridge => ../ai-bridge, pointing at a sibling working copy that does not exist outside the author's machine — every fresh checkout (CI included) will fail go build/go test/go mod download because the replace forces resolution against a missing local directory. Drop the replace before merging and keep only the pinned v0.0.0-20260531201429-3d0bf92ccf00. (fixed by commit ae266ba)

CI Checks

All CI checks passed on commit ae266ba.


⚡ Autofix All Issues

Remove the replace directive that pointed github.com/beeper/ai-bridge to a local ../ai-bridge path in go.mod so the module is resolved from its published version. go.sum is updated with checksum entries for the resolved ai-bridge pseudo-version.
Comment thread go.mod Outdated
Comment thread pkg/connector/ai_text.go
for i := min(limit, len(text)); i >= minCutoff; i-- {
if text[i-1] == ' ' {
return strings.Trim(strings.TrimSpace(text[:i]), ".,;:")
for i := min(limit+128, len(text)); i > limit; i++ {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Crash: this loop increments i while requiring i > limit, so it walks past len(text) and text[i-1] panics with index out of range. Earlier in the function we return when len(text) <= limit, so on entry len(text) > limit and i starts at ≥ limit+1; i++ keeps the condition true forever. This is reachable any time the descending loop above doesn't find .!? in [3/4*limit, limit] — very common for the short chars values that flow through buildLoremTexttrimTexttrimCompleteText. Walk forward with an upper bound instead, e.g. for i := limit + 1; i <= min(limit+128, len(text)); i++ { … }.

Comment thread pkg/connector/client.go
log.Warn().Err(err).Str("approval_id", approvalID).Msg("Timed-out AI approval message was not found")
return
}
response := aistream.TimedOutApprovalResponse(approvalID)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Receiver mismatch with waitForMessageMXID. That helper tries both portal.Receiver and dc.UserLogin.ID to resolve the approval Matrix event, but this timeout fallback only queries portal.Receiver. Whenever the approval message was persisted under the alternate receiver, dc.lookupMessage returns nil and the timeout silently drops (just a Timed-out AI approval message was not found warning), so the run stays paused forever. Iterate the same receiver list here, or expose a lookupMessageByAnyReceiver(...) helper used by both paths.

Comment thread pkg/connector/client.go
@@ -1034,44 +1225,112 @@ func approvalContextFromAny(value any) (aistream.ApprovalContext, bool) {
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lost fallback for stringified ApprovalContext. The previous implementation also handled case string: return approvalContextFromJSON([]byte(typed)) and a generic json.Marshal/Unmarshal round-trip. The new switch only accepts aistream.ApprovalContext, map[string]any, json.RawMessage, and []byte. Any caller that lands an approval context as a JSON-encoded string (or any other shape) will hit the bare return aistream.ApprovalContext{}, false and the approval continuation will be dropped without explanation. Either restore the string/default JSON fallback or document why this shape is now impossible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant