Fixes#4653
## Problem
The /v1/responses endpoint (Responses → Anthropic bridge) failed when
upstream providers returned SSE events in compact format without spaces
after colons (e.g. 'event:message_start' instead of 'event: message_start').
This affected Kimi API (api.kimi.com) and potentially other upstreams,
causing 'Upstream stream ended without a response' errors.
## Root Cause
The SSE parser in gateway_forward_as_responses.go used strict prefix
matching with hardcoded spaces:
if !strings.HasPrefix(line, "event: ") { ... }
if !strings.HasPrefix(dataLine, "data: ") { ... }
According to the SSE spec
(https://html.spec.whatwg.org/multipage/server-sent-events.html),
the space after the colon is optional. Both formats are valid:
event: message_start ✓
event:message_start ✓
## Solution
Introduced parseAnthropicSSEField() helper that:
- Accepts both 'field: value' and 'field:value' formats
- Uses TrimSpace() to handle optional/multiple spaces
- Returns (value, ok) for consistent error handling
Applied to both streaming handlers:
- handleResponsesBufferedStreamingResponse
- handleResponsesStreamingResponse
## Testing
Added comprehensive unit tests:
- TestParseAnthropicSSEField: validates parser with 8 edge cases
- TestHandleResponsesBufferedStreamingResponse_CompactSSEFormat
- TestHandleResponsesStreamingResponse_CompactSSEFormat
All existing tests pass, confirming backward compatibility.
Resolve const-block conflict in openai_gateway_grok_cache.go:
keep #4590's client tool cache constants, drop grokFreeRolling24hTokenLimit
(moved to pkg/xai as IsGrokFreeRolling24hTokenLimit with legacy 2M support).
Grok OAuth 'test connection' reused the production GrokTokenProvider
request path, whose eligibility gate rejects any account that is not
currently schedulable (manual switch off, rate limited, overloaded,
temporarily cooled down). Every such test failed before reaching xAI
with the generic 'oauth refresh account state changed', while
Codex/OpenAI account tests read credentials regardless of scheduling
state (#4598).
Add GetAccessTokenForManualTest and use it from the account test
service:
- skips the request-path scheduling eligibility gate so admins can
probe excluded accounts; credential integrity checks (configured
proxy present, refresh token present) still apply
- refreshes expired tokens through the shared OAuthRefreshAPI without
the request-path marker, so the lock protocol and DB re-read keep
working while scheduling state no longer blocks the refresh;
credential rotation for non-active accounts stays blocked inside
RefreshIfNeeded
- falls back to the still-valid current token when a refresh attempt
fails or another worker holds the refresh lock, and otherwise
surfaces the concrete refresh error instead of the generic message
Fixes#4598
The in-place update ran entirely inside c.Request.Context(). Browsers and
reverse proxies commonly abort long-idle requests (axios global timeout
30s, nginx proxy_read_timeout 60s by default), which canceled the request
context mid-download and killed every slow update with
'download failed: context canceled' while the version stayed unchanged.
Users behind slow GitHub links saw the update button fail at a wall-clock
ceiling (~60s) on every attempt (#4504).
- Run PerformUpdate and RollbackToVersion on a context detached from the
request (context.WithoutCancel) and bounded by a 15-minute deadline so
the 10-minute GitHub download client owns its own timeout. A client
disconnect no longer aborts the binary swap; retries then hit the
system operation lock or report 'Already up to date'.
- Raise the frontend timeout for the update/rollback calls from the
global 30s axios default to 15 minutes so the browser can actually
wait for the result.
Fixes#4504