With the credit config now visible to the scheduler, every scheduling pass
notifies the auto-reset worker for paused credit-enabled accounts.
evaluateAccount forced an upstream query whenever the reset threshold was
reached, regardless of the last result. Accounts with no credits left and
7d >= 95% were therefore polled on /wham/usage every ~15-20s across
instances (previously once a minute by the scanner), and during upstream
outages the failed state caused the same storm.
- At the reset threshold, skip the forced query while a no_credit result is
still within the state TTL (10 min); re-query once it expires so newly
granted credits are still picked up.
- After a query-stage failure (RESET_CREDIT_QUERY_FAILED,
USAGE_SNAPSHOT_WRITE_FAILED, RESET_CREDIT_DETAILS_UNAVAILABLE), wait one
minute before querying again, including snapshot-staleness triggers.
- Redemption-stage failures are unaffected and still retry immediately with
the same idempotency key; accounts with available credits still query and
redeem immediately.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Chat Completions file parts already become Responses input_file, but the
Antigravity path dropped them when converting to Anthropic and then to
Gemini. Map data-URI input_file to a document block and emit it as
inlineData so Gemini receives the PDF.
Fixes#7583
The personal-access-token /v1/alpha/search fallback turned any upstream
Responses SSE body into a 200 search result and billed one web search call.
Error, response.failed and response.incomplete events, a response.completed
with a non-success status or without its response, a truncated stream and a
bare [DONE] were all accepted, returning partial text as a successful and
billable search.
Parse the stream strictly: those shapes now return an error, so nothing is
written to the client and no WebSearchCalls result is produced. A successful
response.completed keeps the existing output and citation handling. The
existing PAT metadata test now feeds a Responses SSE stream, which is what
that upstream path returns.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The API-key Responses capability probe treated every 404 as conclusive and
stored responses_supported=false. The probe model is the first mapped
upstream model in lexical order, so on third-party accounts it is often an
auxiliary entry such as codex-auto-review; a model_not_found answer then
permanently routed the account away from /v1/responses although the
endpoint works.
A 400/404 whose error code/type or message says the probe model does not
exist or is unavailable is now inconclusive, so the stored capability is not
overwritten. Plain 404/405 still mean the endpoint is missing. The probe
also prefers a general gpt-* text model (not an image model) among the
mapped models before falling back to the first one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a channel pricing entry left image_output_price empty,
GetModelPricingWithChannel and the pricing resolver zeroed the image
output price and marked it explicit, so image output tokens were billed
at $0. image_input_price followed the same "unset means zero" rule and
fell back to the text input price instead of the catalog image input
price. A channel that lists an image model such as gpt-image-2 in token
mode without filling in prices therefore generated images without
charging for image output tokens, while the available-channels view
(#2475) displayed the catalog image price for the same entry.
#2930 wanted an explicit 0 on the channel to stop falling back to the
catalog; the change applied the same treatment to unset values as well.
Treat the two image fields like every other token price field:
- Set on the channel (including 0): override the catalog. An explicit
image_output_price keeps ImageOutputPriceExplicit, so 0 stays free.
- Left empty: keep the catalog price (LiteLLM or built-in fallback). If
the catalog has none, computeTokenBreakdown still falls back to the
text input/output price.
The admin UI saves an empty field as null and only an entered 0 as 0,
so the two cases stay distinguishable. The flat, interval and
GetModelPricingWithChannel paths now share applyChannelImagePriceOverrides.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The canonical Anthropic stream leaves tool_use input empty on
content_block_start and streams the arguments as input_json_delta.
Anthropic-compatible relays may instead put the complete arguments on
content_block_start and send no delta. The Anthropic-to-Responses stream
converter ignored that input, so function_call_arguments.done carried ""
and the closed item "{}", and clients executed the tool with no arguments.
Keep the inline input as a seed. A real input_json_delta supersedes it, so
canonical streams keep their exact event sequence and two JSON documents are
never concatenated. When no delta arrived by content_block_stop, emit the
seed as a single arguments delta before the done event, which still repeats
exactly what the deltas streamed. Empty, "{}" and null inputs keep the
existing "{}" fallback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BufferedResponseAccumulator.SupplementResponseOutput rebuilt the output only
when the terminal response had an empty output array. Upstreams can also end
with a non-empty output whose message carries no (or only blank) output_text.
The buffered Chat Completions and Anthropic Messages paths then returned an
empty reply although the text had streamed, while usage still billed the
terminal output_tokens.
When the terminal output has no usable message text, fill the first empty
output_text part (or append one, or append a message item) from the
accumulated deltas. Non-empty terminal text stays authoritative, and nothing
is synthesized when no text streamed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
isReasoningModel matched the literal "gpt-5" prefix plus the GPT-6 Sol/Luna
spellings, so gpt-6 and gpt-6-astra (both already in the model catalog and
price list) fell into the sampling branch. Anthropic Messages and Chat
Completions requests converted to the Responses API then forwarded
temperature/top_p, which the Responses API rejects for these models with
"Unsupported parameter".
Key the check on the parsed GPT generation number instead, keeping the
existing Sol/Luna spelling match, so later families are covered without
another edit. Non-generation GPT families (gpt-image-1, gpt-audio) and
gpt-4.x are unaffected.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
OpenAI candidate filtering reads the Redis metadata projection, but
filterSchedulerExtra dropped auto_reset_credit_enabled, the 5h/7d
reset-credit thresholds and codex_auto_reset_credit_state. The scheduler
therefore always saw automatic reset credits as disabled, so accounts
were paused at the ops quota auto-pause threshold even when a fresh
state showed an available reset credit. The "keep scheduling until the
reset-credit threshold" branch never took effect, and accounts with
credits stayed idle until their window reset naturally.
- Keep the four keys in the scheduler projection; the test round-trips
the real JSON payload and resolves the credit config from it.
- Once visible, every scheduling pass notifies the auto-reset worker for
paused credit-enabled accounts, and each notification reloads the
account. Add a 30s per-account cooldown for the scheduler hot path
only; other event-driven notifications are unchanged and the
per-minute scan remains the fallback.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#7395 made Codex (and Grok) imports store a base URL ending in /v1, but the
usage script still appends a hard-coded /v1/usage, so CC Switch requests
/v1/v1/usage, gets a 404 and shows "query failed" for every newly imported
Codex/Grok provider. Move the script into ccswitchImport.ts and strip an
existing trailing /v1 from {{baseUrl}} before appending /v1/usage, which
covers both the imported value and base URLs users edit either way later.
Close the existing catalog identity test before the new capability test and
assert that an explicit null capability remains present. Update the quota
snapshot fixture to the pointer parameter already used by the current base.
Verified six full backend unit packages, scoped lint and CGO-disabled build.
A single passthrough account made GetAvailableModels return nil for the whole
group, so every other account's model_mapping was dropped from /v1/models and
the Codex catalog. Treat passthrough accounts like unmapped ones: skip their
(possibly stale) mapping and let supplementUnmappedOpenAIModels add the
default set, while ordinary accounts' mappings still reach the list.
Refs #6647, #7517