A plugin change can leave a task model without a usable price: no price
is set, the price uses usage the plugin no longer provides, or the
plugin no longer offers the model. The gateway now lists these models.
The list follows the plugins currently serving, so a gateway update
that changes a built-in plugin shows up too.
- Uploading, installing from the marketplace, activating or enabling a
plugin, or deleting its active version, lists the models it cannot
charge in the upload dialog or in the notice after the action.
- Model pricing marks each of them "Price needs attention" with the
reason, and a "Price status" filter lists them. A plugin model that
was never priced now appears in the table too. The badge replaces
"Task pricing not configured".
- The sidebar shows how many models need attention on System Settings,
Billing & Payment and Model Pricing, for super administrators.
- GET /api/option/model_pricing/issues returns the list. It is root
only; access tokens need the option:read scope.
- Prices kept for a switched-off plugin are not reported. They apply
again when the plugin is switched back on.
Azure channels with responses_websocket_enabled were rejected by
FilterResponsesWebSocket, and the channel form hid the toggle for type 3.
The OpenAI adaptor already builds the Azure /openai/v1/responses URL with
api-version and sets the api-key header, and DoWssRequest reuses both, so
no relay URL change is needed.
OpenAI added a 12-hour prompt cache to the Responses API on 2026-10-20
(prompt_cache_options.ttl "12h", GPT-5.6 and later). A 12-hour write
costs 2.5x the input price, twice the 30-minute write. We already
forwarded the option but billed every cache write at the 30-minute price.
- Read cache_write_tokens_details from Responses usage and keep the split
through streaming, compaction and format conversion.
- Ratio pricing bills 12-hour writes at twice the cache write ratio, the
same way Claude 1-hour writes use 6/3.75.
- Expressions get a cc12h price. When an expression leaves the long-lived
price out, 12-hour writes cost twice the cc price and Claude 1-hour
writes 6/3.75 times it; without a cc price they bill as input. Claude
cache writes are no longer free when an expression omits cc or cc1h.
- gpt-6-astra prices 12-hour writes at $25 ($50 above 272K tokens).
- GPT models with a cache write ratio convert to expressions with cc12h.
- Chat Completions forwards prompt_cache_options.
- The pricing editor, cost estimator, pricing pages and usage log details
show the 12-hour price and tokens.
When a task plugin claimed a model on a shared host endpoint
(/v1/images/generations, /v1/images/edits, /v1/responses, /v1/videos),
every request for that model went to the plugin, even when an ordinary
OpenAI-compatible channel served the model too. Ordinary channels and
the matching plugin channels now compete for the request under the usual
group, priority, weight, affinity and channel-pin rules. When an
ordinary channel wins, the original request reaches the ordinary relay
unchanged: no plugin decoder, mode check or model rewrite runs, and
retries stay on ordinary channels. When a plugin channel wins, the
plugin handles the request as before and retries stay on its accepted
plugin candidates. Native plugin routes still belong to their plugins.
Ordinary and plugin prices are kept apart. Task expressions return USD,
while ordinary expressions keep their USD-per-million result, so an
ordinary request now fails with 400 model_price_error before any quota
is reserved when the model's expression reads task usage (u()) or is
still inherited by a task plugin on that endpoint. The model pricing
editor can set ordinary OpenAI prices, per returned image with
fixed(...) * image_count, while each plugin keeps its price in its own
override. A model served by a single plugin now shows that plugin's
pricing editor as well.
Upstream price sync saved an upstream's price as it was, so a price
quoted in another currency had to be corrected by hand afterwards. Each
selected model now has a price adjustment: keep the source price, or
divide or multiply it by the site exchange rate. "Set selected prices"
applies one choice to every selected model. Fixed prices, ratios and the
prices inside billing expressions are adjusted; an expression that
cannot be adjusted automatically has to keep its source price or be
edited by hand. The confirmation dialog shows the adjustment, the final
saved price and the price in the site currency, with the exchange rate
in use.
"Show models with matching prices" also lists upstream prices that
already match the local ones, so they can be selected and adjusted too.
Order the keys of every web locale file the way localeCompare sorts them,
with numeric keys first as JSON objects keep them. No key or translation
is added, removed or changed.
* feat(email): add Resend delivery provider
Add Resend as an alternative to SMTP with separately editable sender address
and name, credential-safe configuration status, and validation before activation.
Preserve the target repository's email notification and balance reminder settings.
Only Resend changes are transplanted from hicoderai/new-api; no landing,
legal-document, model-group, pricing, branding, or dependency changes are included.
Validation: Go common/controller email regression tests; 19 frontend email and
notification tests; frontend production build; tsgo typecheck; touched-file lint.
Frontend validation uses the target lockfile's Axios 1.18.1 with local cached tools.
Security references: OWASP Authentication, Session Management, and Forgot Password
Cheat Sheets. Tests cover credential non-disclosure, invalid activation, sanitized
transport errors, and no automatic retry or fallback; token and session flows
are unchanged.
https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.htmlhttps://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.htmlhttps://cheatsheetseries.owasp.org/cheatsheets/Forgot_Password_Cheat_Sheet.html
* fix(email): log Resend rejections and translate Resend errors
Resend's reason for a rejected or failed send now goes to the server log.
People who asked for the email still see the short error.
Resend and email method errors are translatable messages in all seven
languages.
The email settings hints are shorter and reuse the existing key hint.
The new frontend files carry the license header, the sender name check
passes lint, and test fixtures use neutral names.
* fix(email): check Resend settings while Resend is active
Saving the sender address or API key now checks the whole Resend setup
while Resend sends the mail. A typo the form accepts, such as
notify..team@example.com, is rejected instead of breaking every send.
Resend's error text and network errors are quoted in the server log.
---------
Co-authored-by: CaIon <i@caion.me>
* fix(model): find channels for @-modified model names without the memory cache
With the channel memory cache off (no Redis and MEMORY_CACHE_ENABLED
unset), channel selection matched abilities by the exact requested name,
so a name such as deepseek-flash@effort:max found no channel. Retry with
the routing name when the exact name has no channel, as the cached path
already does.
* feat(deepseek): support V4.1 Flash, the Search and Fetch APIs, and @ reasoning modifiers
Follows the DeepSeek API update of 2026-10-17.
- Default models: deepseek-flash and deepseek-v4-pro, each with -none,
-low and -max, plus deepseek-search and deepseek-fetch. The retired
deepseek-chat, deepseek-reasoner and deepseek-v4-flash names are no
longer listed.
- Thinking: the -none/-low/-max tails also work on deepseek-flash.
@thinking and @effort now reach DeepSeek's own fields on Chat
(thinking, reasoning_effort), Anthropic (thinking, output_config.effort)
and Responses (reasoning.effort), and take precedence over the tails.
- Built-in prices: peak/off-peak expressions for deepseek-flash (also the
deepseek-v4-flash aliases) and deepseek-v4-pro, and $0.75 per 1,000
requests for deepseek-search and deepseek-fetch.
- Server-side search: searches and page fetches DeepSeek runs for Claude
Code and Codex bill at $0.75 per 1,000 under deepseek_search and
deepseek_fetch, whatever model name the client sent. Searches whose
results already arrived stay billed when a stream ends before its final
usage; this also applies to Alibaba's Anthropic endpoint.
- New endpoints POST /v1/tools/search and /v1/tools/fetch relay the
DeepSeek APIs like /v1/alpha/search. A call is charged only when
DeepSeek returns results or page content.
* fix(deepseek): price /v1/tools/search and /v1/tools/fetch by endpoint
A model named in the request body chose the billing model, so a body such
as {"query":"...","model":"deepseek-flash"} was priced as a chat model with
no tokens and the search or fetch cost nothing. Both endpoints now always
use their routing name, deepseek-search or deepseek-fetch. DeepSeek ignores
the body field, so the body is still forwarded unchanged.
* fix(deepseek): bill Claude-format page fetches at the DeepSeek Fetch price
usage.server_tool_use.web_fetch_requests always went to the web_fetch
tool price, which has no built-in value, while only searches used the
DeepSeek key. A channel can now name its own fetch key next to the search
key, and DeepSeek channels bill reported fetches under deepseek_fetch. A
channel retry clears it like the search key.
SMTP settings now only decide whether mail can be sent. Verification
codes, password reset, email binding and account security mails depend
on SMTP alone, and no notification switch affects them.
Admins get a site-wide low balance reminder switch, on by default, next
to the SMTP settings. The default threshold is entered as an amount.
Users choose which notifications they receive. Low balance reminders are
on for everyone by default. Admins can also turn channel notifications
and upstream model update notices on or off. The root user receives
channel notifications until it turns them off. Other admins receive them
after they turn them on.
A low balance reminder is sent once, when a charge takes the balance
below the threshold. A top-up above the threshold arms it again.
Channel notifications reach every admin who has them on, in that admin's
language. Email binding codes and account security mails follow the
account's language.
Saving notification settings no longer clears the language, the sidebar
layout or the billing preference.
Existing settings keep their behavior, and old clients that do not send
the new fields keep the stored switches. Webhook, Bark and Gotify
payloads are unchanged.
Keep table drop targets stable, use compact previews, and prevent
accidental mode switches and leftover row transforms.
Add first-use guides for card and table views with automatic playback,
pause and replay controls, and translations for all seven locales.
Support priority ordering in table and card views with automatic priority calculation, same-tier and between-tier drop targets, live previews, conflict detection, and undo.
Keep drag mode off by default and place its toggle before Batch Operations. Consolidate sorting controls and preserve the shared visibility and view controls.
A task plugin can offer two billing modes, such as per task for some
models and per second for others. An administrator who prices a model
per second leaves its per-task price at zero. The model card, the list
view and the detail page then showed that unused zero price next to
the real one, and it read as a free item.
A usage price that is zero in every tier is now hidden when another
usage price is set. A model priced at zero throughout still shows its
zero prices. The card also drops the tier name when a model has a
single tier, since that name was always the editor's default "base".
Dark Reader checks html.dark / color-scheme only once during page load,
but the theme class was added by ThemeProvider after React mounted.
Resolve the saved theme in an inline head script and declare
color-scheme: dark on .dark.
The usage-log model badge resolves its icon from MODEL_PROVIDER_RULES by model
name only. clef (Cloudflare) and the NVIDIA families without an nvidia prefix
(nv-embed, nv-rerank, riva-, parakeet-, canary-, nemoretriever) matched no rule
and fell back to the plain dot.
Co-authored-by: xuu1998 <xuu1998@users.noreply.github.com>
Add an opt-in Mantle interface with top-level cache_control, API key and
SigV4 authentication, and native Claude response handling. Keep InvokeModel
as the default for existing AWS channels.
Strip hop-by-hop headers before signing and map the dated Haiku alias to
Mantle's model ID. Cover signatures over HTTP/1.1 and HTTP/2, cache usage,
legacy routing, and channel configuration with regression tests.
Refs #4791
The Sankey labels were hard-coded to #475569, which is nearly invisible
on the dark theme. Read the app's --foreground token at runtime instead,
so labels and the active node outline match the surrounding text in
light/dark mode and every theme preset, and follow preset switches live.
The search box in the console header listed every page, so regular
users saw the admin pages and only found out on click that they had
no access. It now offers the same pages as the sidebar, which already
filters them by role and by the sidebar settings.
The layout now renders the search dialog instead of the search
provider, which removes the import cycle between the two.
The hailuo, vidu, google and vertex-ai plugins now declare the new_api
upstream, so a New API (type 60) channel can bind them, and each serves its
vendor wire format on prefixed native routes that a downstream gateway
running the same plugin addresses:
- hailuo: /hailuo/v1/video_generation and /hailuo/v2/video_generation, with
path-parameter queries. Through a gateway, v1 videos download from the
upstream's task artifact, since only the upstream can resolve the MiniMax
file link.
- vidu: /vidu/ent/v2/{text2video,img2video,start-end2video,reference2video}
and the creations query; the route names the action, so one or two
reference images stay a reference request.
- google: models/{model}:predictLongRunning and operation polling. The
operation is renamed after the gateway task, and videos download from the
upstream's task artifact, since the Gemini file needs the upstream key.
- vertex-ai: one dynamic route for predictLongRunning and
fetchPredictOperation. Operation names echo the caller's project and
location and never carry the upstream project.
Status presenters render a task that is not polled yet as pending and a
task the poller failed while its snapshot was still running as failed, so
the downstream gateway reads the upstream state and settles on the same
usage.
Versions: hailuo 1.3.0; vidu, google and vertex-ai 1.1.0.
* fix(vertex): pass project_id in API Key mode to avoid 404 on official endpoints
Closes#6250.
Vertex AI channel in vertex_key_type=api_key mode: getRequestUrl passed
an empty string as project_id to BuildGoogleModelURL /
BuildAnthropicModelURL, leaving the URL without the
/projects/{PROJECT_ID}/locations/{REGION}/ segment, so the official
Vertex endpoints returned 404.
In JSON mode project_id is parsed from the service account, so the bug
is specific to api_key mode.
Fix:
- Add VertexProjectID to ChannelOtherSettings (used only in api_key
mode; JSON mode keeps parsing from the service account, unchanged).
- In adaptor.go's api_key branch, use ChannelOtherSettings.VertexProjectID
instead of the hardcoded empty string. When unset, fall back to the
legacy behavior so existing api_key channels are not broken.
- Frontend (web/default): show a Project ID input when
VertexKeyType=api_key, persist settings.vertex_project_id, and require
it via superRefine so users cannot save an api_key channel that would
still 404. Clear the residual field in non-api_key mode or non-Vertex
channels.
- Add translations across 7 locales (en/zh/zh-TW/ja/fr/ru/vi).
Regression tests (relay/channel/vertex/adaptor_api_key_url_test.go)
cover 4 scenarios:
- api_key mode + configured project_id -> URL contains /projects/.../locations/
- api_key mode + stream suffix -> key joined with &, path contains project_id
- api_key mode + Claude mode -> anthropic publisher path
- api_key mode + empty project_id -> falls back to legacy behavior
* fix(vertex): make the API Key project ID optional
Express-mode API keys are documented without a project, so a required
project ID would block saving those channels. Without one the URL keeps
the express-mode form as before; with one, the URL carries the project
and the deployment region, so a global region is honored.
- Drop the required check and describe the field in plain words.
- Treat vertex_project_id as a sensitive field, like the key format.
- Assert exact request URLs in a table test, including the global case.
- Cover saving, clearing and loading the project ID in the channel form.
Add a per-token "Allow querying user quota" switch. When it is on,
GET /api/usage/token/ also returns user_quota with the account's
remaining and used quota in quota units. New tokens start with the
switch on: the console form, POST /api/token/ without the field, and the
default token created at registration. Tokens created before this change
stay off. PUT /api/token/ keeps the stored value when the field is
absent, and the token audit lists the field when it changes.
The key usage and key log queries now check the key's IP whitelist the
same way API requests do, and answer 403 from an address outside it.
Expired and exhausted keys can still use both queries.
New API channels now query their balance from the upstream's
/api/usage/token/ with the channel key. When the upstream also returns
the owner's quota, the smaller of the key's and the owner's remaining
quota is the balance, and an unlimited key uses the owner's quota alone.
Without it, a limited key reports its own remaining quota, and an
unlimited key asks for the switch to be turned on upstream. Quota
converts to USD with the upstream's quota_per_unit from /api/status.
Refs #3122
Refs #6828
* feat(logs): export usage logs from the common log page
The common log page has an Export button next to Search. It asks for a
time range and downloads the logs of the current view, with the page's
other filters, as a CSV file in the viewer's language. Users export their
own logs; administrators in the all-users view export every user's logs.
Each user runs one export at a time. A second export started while one is
running, from another tab or another node, is refused until the first one
ends. With Redis the reservation holds across nodes and lapses within a
minute if its node stops.
Log Maintenance settings add two options: whether users may export (on by
default) and the longest log time range in days (30 by default). Regular
users can search their logs within that range at once, and every export
is limited to it. A user's log search had no limit before.
* fix(web): split the log maintenance page into four modules
The log maintenance page ran its settings, the history log cleanup and
the server log files together. It now shows four modules, each under its
own heading and separated by a divider, like the performance page: log
recording, log search and export, history log cleanup, and server log
management. The history log cleanup no longer sits in a box of its own.
The history log cleanup and the server log files are now components of
their own; what they do is unchanged.
* feat(logs): default the log time range to 365 days and apply it to the dashboard
The longest log time range now defaults to 365 days instead of 30.
Regular users had no limit on log searches before this setting, and 30
days could not cover a 31-day month.
A user's own usage and traffic charts on the dashboard were held to a
fixed one-month range. They now follow the same setting, so the
dashboard, the log search and the export share one limit, and a range
that is too long gets the same "cannot exceed N days" message.