Skip to content

fix: preserve transformed Responses stream events - #2365

Merged
looplj merged 1 commit into
looplj:unstablefrom
MrLiuGangQiang:fix/responses-session-stream-events
Sep 3, 2026
Merged

looplj merged 1 commit into
looplj:unstablefrom
MrLiuGangQiang:fix/responses-session-stream-events

Conversation

@MrLiuGangQiang

@MrLiuGangQiang MrLiuGangQiang commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Summary

Cache the event consumed by responsesSessionStream.Next and return that cached event from Current instead of calling inner.Current() a second time. Add a regression test proving every wrapped event reaches the downstream consumer.

Root cause

For transformed (non-pass-through) Responses streams, responsesSessionStream.Next reads one event from the inner stream to record session data, and Current then calls inner.Current() again. Queue-backed streams such as responsesInboundStream advance their cursor on every Current() call, so events were consumed in pairs:

  • Next consumed event 0 internally
  • the client received event 1
  • Next consumed event 2 internally
  • the client received event 3

The client never received response.completed, so the request ended as stream ended without terminal event or completed response.

Native Responses pass-through masked the bug because its Current() returns a cached pointer and is idempotent.

Impact

Affected when all of the following are true:

  1. downstream API format is openai/responses
  2. stream=true
  3. response pass-through is not applied
  4. the final Responses event stream has consumptive Current() semantics

This includes Responses requests transformed through Chat Completions, Anthropic, Gemini, or a non-pass-through Responses path. Both SSE and downstream Responses WebSocket use the same Next/Current contract.

Verification

  • Added TestResponsesSessionStreamPreservesEveryEventForConsumer.
  • No build or lint commands were run locally.

Related

Summary by CodeRabbit

  • Bug Fixes
    • Improved streaming event handling to ensure consumers receive every event in the correct order and unchanged.
    • Preserved terminal events reliably when processing streamed responses.

responsesSessionStream read each inner event in Next() and then called inner.Current() again from Current(). Consumptive transformed Responses streams advance on every Current() call, so events were skipped in pairs and response.completed never reached the client, surfacing an incomplete-stream error. Cache the event consumed by Next() and return it from Current(), clearing it when the stream is exhausted.

Adds a regression test verifying every wrapped event reaches the downstream consumer.
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: e2e332a0-44bf-4b13-a1d7-a56b5ed78fc3

📥 Commits

Reviewing files that changed from the base of the PR and between 147e679 and 99c28fa.

📒 Files selected for processing (2)
  • internal/server/orchestrator/responses_session.go
  • internal/server/orchestrator/responses_session_test.go

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The response session stream now caches its current event during iteration. Exhaustion clears the cache, and consumers read the cached event. A regression test verifies that wrapped streams preserve all upstream events.

Changes

Response stream handling

Layer / File(s) Summary
Cache and preserve stream events
internal/server/orchestrator/responses_session.go, internal/server/orchestrator/responses_session_test.go
responsesSessionStream caches the current event for accumulation, terminal-event checks, and Current(). The test verifies that response.created, response.output_item.added, and response.completed reach the consumer unchanged.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 99c28

Responses stream consumers now receive each transformed event, including terminal completion events, rather than advancing the underlying stream a second time. The targeted regression coverage supports the intended behavior with no remaining merge-blocking risk.

Suggested reviewers: techblack

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: preserving transformed Responses stream events.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@greptile-apps

greptile-apps Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes transformed Responses streams by caching the event consumed during Next() and returning that same event from Current().

  • Prevents consumptive inner streams from skipping alternating events.
  • Clears the cached event when the inner stream is exhausted.
  • Adds a regression test confirming that every wrapped event reaches the consumer.

Confidence Score: 5/5

The PR appears safe to merge, with the stream wrapper now preserving every event for downstream consumers.

The changed wrapper follows existing consumer iteration patterns, preserves terminal recording through its independent chunk buffer, and avoids the prior second read from potentially consumptive inner streams.

Important Files Changed

Filename Overview
internal/server/orchestrator/responses_session.go Caches each event during Next() so session recording and downstream delivery observe the same stream item without a second consumptive read.
internal/server/orchestrator/responses_session_test.go Adds focused regression coverage demonstrating that all wrapped stream events are delivered in order.

Reviews (1): Last reviewed commit: "fix: preserve transformed Responses stre..." | Re-trigger Greptile

@looplj
looplj merged commit 1908ca2 into looplj:unstable Sep 3, 2026
7 checks passed
caolib added a commit to caolib/axonhub that referenced this pull request Sep 4, 2026
* upstream/unstable: (91 commits)
  fix(openai): complete cross-format request conversion (looplj#2377)
  fix: window deploy scripts, close looplj#2376 (looplj#2386)
  feat: add ZenMux channels and quota-aware UI (looplj#2381)
  fix(api): report server stream deadlines to SSE clients (looplj#2362)
  fix(frontend): handle empty channel error messages (looplj#2375)
  fix(frontend): silence transient channel polling errors (looplj#2382)
  fix(trace): stop persisting traces for embedding requests (looplj#2378)
  fix(anthropic): preserve client cache_control breakpoints to keep prompt cache hits (looplj#2342)
  fix(images): accept application/json in passthrough mode for /images/edits (looplj#2336) (looplj#2370)
  fix(openai): prevent zero-status errors from returning HTTP 200 (looplj#2372)
  fix(openai): preserve request contracts across transports (looplj#2374)
  Pass the caller's context through the v0.3.0 and v0.4.0 migrations (looplj#2348)
  fix(openai): tolerate object-shaped usage cost (looplj#2373)
  feat: add opencode session header, close looplj#2361 (looplj#2369)
  fix(anthropic): reject invalid tool input on clean EOF (looplj#2363)
  fix: dockerfile compitable, close looplj#2367 (looplj#2368)
  fix: preserve transformed Responses stream events (looplj#2365)
  fix(codex): strip user field from outbound requests (looplj#2352)
  fix(frontend): repair height-constrained scroll layout in mobile dialogs (looplj#2354)
  feat: show codex reset details, close looplj#2301 (looplj#2360)
  ...

# Conflicts:
#	.github/workflows/docker-unstable.yml
#	.gitignore
#	docker-compose.yml
#	frontend/src/features/models/data/providers.json
#	frontend/src/features/models/data/providers.ts
#	llm/transformer/anthropic/inbound_convert.go
#	llm/transformer/anthropic/thinking_test.go
PowerDi pushed a commit to PowerDi/axonhub-mini that referenced this pull request Sep 4, 2026
responsesSessionStream read each inner event in Next() and then called inner.Current() again from Current(). Consumptive transformed Responses streams advance on every Current() call, so events were skipped in pairs and response.completed never reached the client, surfacing an incomplete-stream error. Cache the event consumed by Next() and return it from Current(), clearing it when the stream is exhausted.

Adds a regression test verifying every wrapped event reaches the downstream consumer.
@MrLiuGangQiang
MrLiuGangQiang deleted the fix/responses-session-stream-events branch September 5, 2026 05:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants