Skip to content

fix: address CR review issues for v3.0.1#27

Merged
john-zhh merged 6 commits intofeat/v3.0.1from
fix/cr-review-issues
Mar 11, 2026
Merged

fix: address CR review issues for v3.0.1#27
john-zhh merged 6 commits intofeat/v3.0.1from
fix/cr-review-issues

Conversation

@john-zhh
Copy link
Copy Markdown
Contributor

Summary

Addresses all 7 issues raised in the code review for PR #26.

  • [HIGH] OpenAI Chat gets wrong protocol after /responses retry: copyResponseFromResponsesAPI now branches on client format and transforms to Chat Completions for openai-chat clients. StreamTransformer short-circuit fixed to handle openai-responses → openai-chat cross-format conversion. Added ResponsesAPIToOpenAIChat and transformResponsesAPIToOpenAIChat.
  • [MEDIUM] Round-robin counter pollution: Scenario routes now use profile:scenario:<name> as the counter key, keeping scenario and default pool counters independent.
  • [MEDIUM] Streaming session usage lost: sseUsageExtractor wraps the SSE response body and parses message_start/message_delta usage events in-flight, calling UpdateSessionUsage on EOF.
  • [MEDIUM] Concurrent shutdown panic: sync.Once replaces the racy select/close so concurrent POST /daemon/shutdown cannot double-close shutdownCh.
  • [MEDIUM] Session data race: handleGetSessions now copies SessionInfo struct values (not pointers) under the read lock before encoding.
  • [MEDIUM] Lock contention fallthrough: Added explicit return fmt.Errorf(...) when daemon didn't start after waiting, preventing two waiters from racing into startDaemonBackground.
  • [MEDIUM] Test gaps: TestLoadBalancer_RoundRobinPerProfileIsolation now has a real t.Errorf assertion. Added TestResponsesAPIRetryOpenAIChat for the openai-chat retry path.

Test plan

  • All unit tests pass (go test -race ./...)
  • New TestResponsesAPIRetryOpenAIChat passes and catches the bug

🤖 Generated with Claude Code

john-zhh and others added 6 commits March 11, 2026 18:31
- Use sync.Once to close shutdownCh so concurrent POST /api/v1/daemon/shutdown
  requests cannot race to close an already-closed channel (panic)
- Copy SessionInfo struct values (not pointers) before releasing the read lock
  in handleGetSessions to eliminate the data race window between unlock and
  JSON encoding

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When startDaemonBackground encounters ErrLockContention it waits for the
lock holder to finish, then checks IsDaemonRunning. Previously, if the
holder's launch failed, two or more waiters would both fall through to
the child-process start path without holding the lock, causing concurrent
duplicate starts. Now return an explicit error so the caller retries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LoadBalancer.Select was called with the same profile key for both
scenario-route and default-fallback selections. Each call advanced the
shared profile counter, so scenario traffic would silently perturb the
default provider order. Pass a distinct key (profile:scenario:<name>)
when selecting for a scenario route so each scenario and the default
pool maintain independent counters.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…fter retry

When a provider returns "input is required" and the proxy retries via
/responses, copyResponseFromResponsesAPI only converted to Anthropic
format. An openai-chat client would receive raw Responses API JSON/SSE.

- Add ResponsesAPIToOpenAIChat (non-streaming) transform
- Add transformResponsesAPIToOpenAIChat (streaming) in StreamTransformer
- Fix TransformSSEStream short-circuit: openai-responses → openai-chat
  requires conversion even though both normalize to "openai"
- Update copyResponseFromResponsesAPI to branch on client format

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…etry coverage

- loadbalancer_test.go: the per-profile isolation test had an empty if body
  so a counter-pollution regression would never be caught; add t.Errorf
- server_test.go: add TestResponsesAPIRetryOpenAIChat to cover the path
  where an openai-chat client retries via /responses and must receive a
  Chat Completions response, not a raw Responses API payload

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ractor

- ResponsesAPIToOpenAIChat: text, tool_call, incomplete status cases
- transformResponsesAPIToOpenAIChat: text stream, tool_call stream
- sseUsageExtractor: parses message_start/message_delta, empty sessionID,
  Close delegation
- Fix transformResponsesAPIToOpenAIChat to flush buffered response.completed
  event when stream ends without trailing blank line (mirrors the existing
  flush logic in transformResponsesAPIToAnthropic)

proxy: 81.8% (was 79.0%), transform: 87.1% (was 79.5%)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@john-zhh john-zhh merged commit d31d6a8 into feat/v3.0.1 Mar 11, 2026
4 checks passed
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.

1 participant