Ambiguous idempotency-store failures can occur after the account transaction commits. Scope durable recovery markers to the authenticated admin, retain the operation key across reloads, and recover only an already committed copy without rerunning active work.
Constraint: Generic idempotent handlers may legitimately remain active while a recovery lookup is attempted
Rejected: Reclaim or rerun an in-progress duplicate request | can execute account creation concurrently
Rejected: Recover by source account and key alone | allows another admin to observe the committed copy
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep ambiguous-response recovery read-only and bind durable operation markers to the authenticated actor
Tested: Full Go unit suite, go vet, server build, integration-test compilation; frontend lint, typecheck, 1,030 Vitest tests, and production build
Not-tested: Docker-backed PostgreSQL integration runtime because Docker is unavailable
Related: Wei-Shaw/sub2api#1379
Related: Wei-Shaw/sub2api#2928
Admins often need another account with the same provider and routing configuration. Duplicate on the server so credentials never return to the browser, preserve exact group priorities atomically, start the copy paused, and recover the same copy after ambiguous idempotency-store failures.
Constraint: Admin account responses redact credentials, so duplication must remain server-side
Constraint: OAuth and setup-token credentials rotate and must not be shared across account rows
Rejected: Copy raw account JSON to the clipboard | exposes credentials outside the server
Rejected: Duplicate rotating credentials | account-scoped refresh locks can race token rotation
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep copies paused, avoid automatic upstream probes, and exclude rotating credential types unless token ownership is redesigned
Tested: Targeted Go tests, Go vet, server build; frontend lint, typecheck, Vitest suite, production build; integration test compiled
Not-tested: Docker-backed PostgreSQL execution because Docker is unavailable
Related: Wei-Shaw/sub2api#1379
Related: Wei-Shaw/sub2api#2928
The refresh button had no effect because two independent gates blocked
the Codex probe:
1. isOpenAICodexSnapshotStale returned false for non-WS-v2 accounts even
when data was stale — this is correct for background auto-refresh (Codex
quota is only auto-tracked for Codex CLI / WS v2 accounts), so this
behavior is preserved.
2. shouldProbeOpenAICodexSnapshot had a 10-min in-memory rate limit with
no bypass for explicit user requests.
Fix: add a force parameter threaded from handler → GetUsage → getOpenAIUsage
→ shouldProbeOpenAICodexSnapshot. When force=true (manual refresh button),
both gates are bypassed and the probe always runs. Background auto-loads
continue to respect the original WS v2 logic and 10-min rate limit.
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
Bug fixes:
- Detached context for GetAccountConcurrencyBatch (prevent all-zero on request cancel)
- Filter soft-deleted users in GetByGroupID
- Stripe CSP policy (allow Stripe.js in script-src and frame-src)
- WebSearch API key validation on save
- RECHARGING status in payment result success check
- Windows test fixes (logger Sync deadlock, config path escaping)
Feature enhancements:
- Webhook multi-instance dispatch (extractOutTradeNo + GetWebhookProvider)
- EasyPay mobile H5 payment (device param + PayURL2)
- SSE error propagation in WebSearch emulation
- AccountStatsCost DTO field for admin usage logs
- Plans sort by sort_order instead of created_at
- UsageMapHook for streaming response usage data
- apicompat Instructions field passthrough
- EffectiveLoadFactor for ops concurrency/metrics
- Usage billing RETURNING balance for notify system
- BulkUpdate mixed channel warning with details
- println to slog migration in auth cache
- Wire ProviderSet cleanup
- CI cache-dependency-path optimization
Frontend:
- Refund eligibility check per provider (canRequestRefund)
- Plan sort_order editing
- Dead code cleanup (simulate_claude_max, client_affinity)
- GroupsView platform switch guard
- channels features_config API type
- UsageView account_stats_cost export
Consolidate two separate channel types (bedrock + bedrock-apikey) into
a single "AWS Bedrock" channel. Authentication mode is now distinguished
by credentials.auth_mode ("sigv4" | "apikey") instead of separate types.
Backend:
- Remove AccountTypeBedrockAPIKey constant
- IsBedrock() simplified; IsBedrockAPIKey() checks auth_mode
- Add IsAPIKeyOrBedrock() helper to eliminate repeated type checks
- Extend pool mode, quota scheduling, and billing to bedrock
- Add RetryableOnSameAccount to handleBedrockUpstreamErrors
- Add "bedrock" scope to Beta Policy for independent control
Frontend:
- Merge two buttons into one "AWS Bedrock" with auth mode radio
- Badge displays "Anthropic | AWS"
- Pool mode and quota limit UI available for bedrock
- Quota display in account list (usage bars, capacity badges, reset)
- Remove all bedrock-apikey type references