1. Redeem code affiliate rebate: balance-type redeem codes now trigger
invite rebate for the inviter. Payment fulfillment uses context key
to prevent double-rebate.
2. Batch concurrency update: new POST /admin/users/batch-concurrency
endpoint supporting mode=set/add with all=true for all users.
3. Markdown page rendering: new GET /api/v1/pages/:slug API serves local
.md files. Custom menu items with url="md:slug" render markdown with
collapsible TOC sidebar, scroll spy, and copy buttons on code blocks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DeriveUpstreamEndpoint hard-codes /v1/responses for PlatformOpenAI,
but APIKey accounts probed to not support Responses API are forwarded
directly to /v1/chat/completions via forwardAsRawChatCompletions.
Add resolveRawCCUpstreamEndpoint which returns /v1/chat/completions
when the account's extra.openai_responses_supported is explicitly false.
Fix four issues flagged by copilot-pull-request-reviewer on PR #2143:
1. Probe URL missing /v1 prefix (openai_apikey_responses_probe.go)
Replaced bare TrimSuffix + "/responses" with buildOpenAIResponsesURL(),
which handles bare domain → /v1/responses correctly. Affected:
- ProbeOpenAIAPIKeyResponsesSupport (probe URL)
- TestAccount endpoint (apiURL for APIKey accounts)
2. Create endpoint not triggering probe (account_handler.go)
Capture created account from idempotent closure and call
scheduleOpenAIResponsesProbe after success, same pattern as
BatchCreate and Update.
3. Tests (openai_gateway_chat_completions_raw_test.go)
Added TestBuildOpenAIChatCompletionsURL (7 cases covering
bare domain, /v1 suffix, trailing slash, third-party domains,
whitespace) and TestBuildOpenAIResponsesURL_ProbeURL (6 cases
locking the probe URL construction for bare-domain inputs).
All unit tests pass; go build ./cmd/server/ clean.
OpenAI APIKey accounts with base_url pointing to third-party OpenAI-compatible
upstreams (DeepSeek, Kimi, GLM, Qwen, etc.) were failing because the gateway
unconditionally converted Chat Completions requests to Responses format and
forwarded to {base_url}/v1/responses, which only exists on OpenAI's official
endpoint.
Detection-based routing:
- Probe upstream capability on account create/update via a minimal POST to
/v1/responses; HTTP 404/405 means 'unsupported', any other response means
'supported'.
- Persist result as accounts.extra.openai_responses_supported (bool).
- ForwardAsChatCompletions branches at function entry: APIKey accounts with
explicit support=false go through new forwardAsRawChatCompletions which
passthrough-forwards CC body to /v1/chat/completions without protocol
conversion.
Default behavior for accounts without the marker preserves the legacy
'always Responses' path — existing OpenAI APIKey accounts that were working
before this change continue to work without modification (the 'reality is
evidence' principle: an account that has been running implies upstream
capability).
Probe is fired async after Create / Update / BatchCreate; failures only log,
never block the admin flow. BulkUpdate omitted (low signal of base_url
changes; can be added if needed).
Implementation:
- New pkg internal/pkg/openai_compat: marker key + ShouldUseResponsesAPI
- New service file openai_apikey_responses_probe.go: probe + persist
- New service file openai_gateway_chat_completions_raw.go: CC pass-through
- Account test endpoint short-circuits with explicit message for
probed-unsupported accounts (full CC test path is a TODO)
Zero schema changes, zero migrations, zero frontend changes, zero wire
modifications — all wired through existing AccountTestService injection.
Closes: DeepSeek-OpenAI account (id=128) production failure
Backend: Fix three race conditions in SetSnapshot that caused account
scheduling anomalies and broken sticky sessions:
- Use Lua CAS script for atomic version activation, preventing version
rollback when concurrent goroutines write snapshots simultaneously
- Add UnlockBucket to release rebuild lock immediately after completion
instead of waiting 30s TTL expiry
- Replace immediate DEL of old snapshots with 60s EXPIRE grace period,
preventing readers from hitting empty ZRANGE during version switches
Frontend: Remove serial queue throttle (1-2s delay per request) from
usage loading since backend now uses passive sampling. All usage
requests execute immediately in parallel.
The hardcoded codex CLI version (0.104.0) causes upstream rejection
when using gpt-5.5 with compact, as the server treats the request
as an outdated client and returns 400/502.
Update codexCLIVersion, codexCLIUserAgent, and openAICodexProbeVersion
to 0.125.0 to match the current Codex CLI release.
Fixes#1933, #1887, #1865
Related: #1609, #1298, #849
- Drop SetAffiliateService setters and ProvideAuthService /
ProvidePaymentService / ProvideUserHandler wrappers in favor of direct
Wire constructor injection. AffiliateService has no back-edge to
Auth/Payment/User, so the indirection was never required.
- Change RegisterWithVerification's variadic affiliateCode to a fixed
parameter; adjust all call sites.
- Validate aff_code length and charset in BindInviterByCode before any
DB lookup, eliminating timing-side-channel and useless DB roundtrips
on malformed input.
- Make affiliate cache invalidation synchronous; surface Redis errors
via the project logger instead of swallowing them in a detached
goroutine.
- Add an integration test guarding cross-layer tx propagation in
AccrueQuota and a unit test pinning the aff_code format rules.
Revert payment/wechat, sora/claude-max cleanup, fork-only migrations,
and cosmetic changes that were brought in by the release sync commit.
Keep only channel-monitor related improvements:
- PublicSettingsInjectionPayload named struct with drift test
- ChannelMonitorRunner graceful shutdown in wire
- image_output_price in SupportedModelChip
- Simplified buildSelfNavItems in AppSidebar
- Gateway WARN logs for 503 branches