Adds two regression cases mirroring the #5009 OpenAI ones for Grok
platform accounts (issue #7774):
- TestOpenAIResponsesEmptyCompletedFailsOverGrok: an empty
response.completed on the forwardGrokResponses streaming path must
produce UpstreamFailoverError with an empty client body. Verified to
FAIL on main without the guard extension and PASS with it.
- TestOpenAIResponsesEmptyCompletedWithOutputSucceedsGrok: normal Grok
streams with semantic output and usage are untouched.
Fixes#7774
Issue #5009 added a failover guard for OpenAI Responses streams that
terminate with an empty response.completed (no output, no usage, no
error), but the guard is gated on PlatformOpenAI. Grok platform
accounts reach the same handler (forwardGrokResponses ->
handleStreamingResponseWithReasoning) and exhibit the same upstream
silent-refusal shape, so their empty completions are relayed to the
client as successful empty turns: Responses SDK clients map the empty
completed to finishReason stop and accept the turn with no retry.
Extend the guard to PlatformGrok, matching the unconditional guard on
the passthrough path (openai_gateway_passthrough.go).
Fixes#7774
- Map five_hour/seven_day/seven_day_overage_included to 5h/7d/7d overage
(raw key fallback) in the confirm message, credit tooltip and success message.
- Align zh/en confirm copy with the Codex dialog; {count} is the count
remaining after this use.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- R1-1: an explicit upstream "unavailable" answer now fences the organization
for 15 minutes (CLAUDE_RESET_UPSTREAM_UNAVAILABLE) instead of 24h; ambiguous
outcomes keep the 24h fence. Frontend tells the operator to retry later.
- R1-2: rotate the idempotency key after definite pre-claim refusals (busy,
not available, unresolved, upstream unavailable); map IDEMPOTENCY_IN_PROGRESS
and IDEMPOTENCY_RETRY_BACKOFF to clear zh/en messages.
- R1-3: redeem reason and cleared windows pass only through explicit
allowlists, so upstream values can never echo a grant ID to clients.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Orange 「重置」 button mirrors the Codex cell: enabled only after a query
shows a redeemable credit, danger confirm, one idempotency key per
confirmation reused on retry, outcome-specific messages, then refreshes
credits and the usage row. Ported from upstream PR #7591.
Co-authored-by: korkin25 <eugeny.kuzakov@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
POST /admin/accounts/:id/claude/reset-credits/redeem (Idempotency-Key
required, same middleware chain as the Codex reset-quota route). The server
re-queries eligibility and claims only the upstream next_grant_id when it is
redeemable; grant/org IDs never reach the client. Claims are serialized by
account and organization Redis leases, and a durable per-organization fence
in the idempotency table blocks further redemption after an unconfirmed
outcome (settles after 24h). Ported from upstream PR #7591.
Co-authored-by: korkin25 <eugeny.kuzakov@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Drop selection_token (unsalted hash of a short grant ID, reversible) and
claudeGrantSelection from the read-only DTO; assert it never serializes.
- query() returns only the projected result; no raw upstream block escapes.
- Clear a cooldown_until that is already past, using the same `now` as the
redeemable check; the cell also shows the cooldown only when in the future.
- Invalidate in-flight queries when account id, platform or type changes.
- Sort the earliest-expiry fallback by parsed timestamp, invalid/missing last.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Show resets held (sum of resets_left) on the count button, matching the
Codex count; flag "not usable now" when none is redeemable yet, instead
of showing 0 while a grant exists.
- Pick the redeemable (next) grant for the expiry chip, falling back to
the earliest expiry, rather than assuming credits[0] is next.
- Render a single ClaudeResetCreditsCell across usage states so a query
started while usage loads is not lost on remount.
- Drop an unrelated google/subcommands go.sum entry.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The Claude reset credit query rendered as a bare "Claude 重置额度" link,
which reads like a redeem action, and dumped raw ISO timestamps and
window keys. Render it like OpenAIQuotaResetCell instead: a read-only
"次数 N" button sharing one row with the local query button via a
#pre-actions slot, plus an expiry chip whose tooltip carries the grant
label, cleared windows and limit requirement.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- only consult account mappings after primary/channel candidates fail to price (or composite)
- source mappings from the scheduler snapshot (same pool as account selection), no direct DB query and no per-model cache
- ungrouped API keys use the scheduler's ungrouped pool
- no negative caching: new mappings take effect on the next request
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Inflight estimate now mirrors billableModelWithFallback: when the primary
billing model has no price, fall back to the channel-mapped model (also for
billing_model_source=requested) and to account-level mapped models (max over
schedulable accounts in the group, cached 30s).
- Composite groups also consider concrete account-mapped models (max) so the
alias' family fuzzy price cannot underestimate.
- Renew Lua refuses (and purges) members whose score <= now.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Address gaps found in adversarial verification of the in-flight balance
reservation:
- Early release: the reservation is now a ref-counted handle carried on the
request context. Billing tasks submitted via submit*UsageRecordTask take a
ref and return it only after RecordUsage has run; with reservation enabled
the balance cache is deducted synchronously inside the billing task, so the
reservation is released only after admission can see the deduction. Dropped
tasks return their ref immediately. The reservation is renewed every ttl/3
while the handler runs; after the handler returns it is no longer renewed,
so the post-handler hold is bounded by one TTL.
- Estimate: use the same billing model (channel mapping + BillingModelSource,
max over requested/mapped when ambiguous) and ModelPricingResolver (group
ModelPricing incl. wildcards, channel per_request/image/video/token
overrides), default/group/user rate + peak multiplier, plus image, video,
audio and search surcharge kinds. Unpriced requests fail open with a
throttled log; new billing.inflight_reservation.fail_closed_on_unpriced
(default false) rejects them instead.
- Wire images, embeddings, web/x search, alpha search, grok media generation,
grok realtime/voice, OpenAI-platform /v1/messages and ResponsesWebSocket
(session-level reservation with renewal).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Balance-mode admission only checked cached balance > threshold, so N
concurrent expensive requests could all pass and overdraw the user.
Add a Redis in-flight reservation: at admission estimate a conservative
per-request cost (input estimate + output price x max_tokens, times rate
multiplier) and atomically require cached_balance - inflight >= estimate
(first in-flight request always admitted). Reservations are released via
defer on every path and expire by TTL. Fails open on estimation / Redis
errors. Configurable via billing.inflight_reservation (enabled by default).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>