6592 Commits
Author SHA1 Message Date
CaIon 206e0fa5c6 fix(kling): bump plugin to 1.1.1 2026-10-20 22:43:11 +08:00
CaIon 5900426821 fix(task): restrict query redirect credentials to allowed hosts
Reuse the channel base host and plugin allowedHosts for redirected query
credentials. Keep anonymous redirects working, prevent credentials from
returning after an untrusted hop, and restrict body replay to trusted hosts.
Preserve SSRF and redirect limits, reject HTTPS downgrades, and redact signed
URLs from redirect-policy errors. Document the existing allowedHosts contract.

Validation: go test ./relay/channel/task/jsplugin -count=1
Passed against clean 6347fb0aa plus this patch, without an overlay.
Covers single/batch polling, 301/302/303/307/308, host and port boundaries,
multi-hop credentials, SSRF, downgrade rejection, and real HTTP receivers.

Related OWASP ASVS v5.0.0 controls: 14.2.3, 1.3.6, 12.3.1, 16.2.5.
https://github.com/OWASP/ASVS/tree/v5.0.0/5.0/en
2026-10-20 22:43:11 +08:00
CaIon 120dbeafb1 test(security): match the email code only as a whole number
The email binding audit test failed now and then because it searched the
whole audit JSON for the six-digit code. Audit event and request IDs start
with a long run of time digits, so a random code sometimes appeared inside
one of them. Treat only a code that no other digit touches as a leak.
2026-10-20 22:43:11 +08:00
CaIon f9a0634f84 test(audit): read PostgreSQL index columns in key order 2026-10-20 22:43:11 +08:00
CaIon 55e2ae54d1 fix(task): reprice each retry and reserve it before submission
A task retry that moved to another plugin or channel kept the previous
attempt's usage expression. A retry from an expression-priced plugin to
one priced per request charged, and later settled, at the first plugin's
price. Each attempt now prices itself from scratch.

A retry to a more expensive channel was topped up only after the
upstream had accepted the task. A user who could not cover the
difference went into arrears, or the task was dropped while the upstream
was already running it. The retry now raises the reservation before the
request is sent, with the same balance check as the first pre-consume,
and a channel the balance cannot cover is never called.

A wallet top-up taken before submission replaced the amount held by the
first pre-consume instead of adding to it, so a request that then failed
refunded only the top-up. Image requests whose quantity was raised
before submission were affected too. The full amount is now refunded.
2026-10-20 22:43:11 +08:00
brushax fc8f7d6fef fix: fall back to CNY when DeepSeek USD wallet is zero (#7917)
DeepSeek /user/balance returns both USD and CNY wallets. An account
funded in CNY reports USD 0.00, and getDeepSeekBalanceUSD returned that
zero before ever reading the CNY entry.

Only short-circuit on a positive USD balance. When the USD wallet is 0
and CNY holds funds, convert CNY with USDExchangeRate; a lone zero USD
wallet still returns 0 instead of an error.
2026-10-20 22:39:42 +08:00
BradleyZ e2a0c4b16a fix(limiter): recover redis script after flush (#6486)
- Retry the Lua rate limiter with EVAL when Redis returns NOSCRIPT
- Add regression coverage for Redis script cache flush recovery
2026-10-20 22:28:33 +08:00
Sheldon a8535fac10 fix(kling): accept single API keys (#7897) 2026-10-20 22:23:32 +08:00
Roger Chienandseefs001 783735607d fix(relaykit): keep tool calls when merging adjacent assistant messages for Claude (#7921)
* fix(relaykit): keep tool calls when merging adjacent assistant messages

Adjacent assistant messages with string content were merged keeping only
the second message's tool calls, so tool results for the first message's
calls had no matching tool_use and Claude rejected the request.

- concatenate both messages' tool calls in input order
- skip the "..." placeholder of a tool-call message in the merged text

* fix(relaykit): preserve literal ellipses in assistant message merges

---------

Co-authored-by: seefs001 <[email protected]>
2026-10-20 22:15:40 +08:00
feitianbubuandCaIon afe73b08ff feat: 支持阿里多模态向量化模型qwen3-vl-embedding (#5179)
* feat: add ali MultimodalEmbedding qwen3-vl-embedding

* fix: report image/video tokens in qwen3-vl-embedding usage for billing

* fix(ali): serve every Bailian multimodal embedding model natively

Route qwen2.5-vl-embedding, the tongyi-embedding-vision series and
multimodal-embedding-v1, dated snapshots included, to the native
multimodal-embedding endpoint like qwen3-vl-embedding. Text embedding
models stay in compatible mode.

Take tongyi-embedding-vision's text and image tokens from
input_tokens_details so the image ratio applies to its image tokens,
and set total_tokens to the billed input tokens: tongyi counts its
unpriced output_tokens in total_tokens. A response without usage bills
the request estimate, as compatible mode does. Return an error code
carried in a 200 body as a 500, close the response body when reading
it fails, and fold the input conversion into one function.

Count the text, image and video parts of embedding input in the request
estimate the way chat content is counted, so image calls are pre-charged
and a response without usage is not free. With the default settings no
image is downloaded for the estimate.

* fix(gemini): reject embedding input that is not plain text

Gemini embeds text only, so the converter kept the strings and dropped
image, video and text parts. The embedding request estimate now counts
those parts, and Gemini embeddings bill that estimate, so a dropped part
would be charged without ever reaching Gemini. A part now fails the
request with a 400 that skips retries, before anything is sent or charged.

---------

Co-authored-by: CaIon <[email protected]>
2026-10-20 21:38:28 +08:00
WingStoneandCaIon 9a42033a61 fix(task): 修复任务提交与轮询的请求头覆盖 (#7918)
* fix(task): honor channel header overrides for submission and polling

Apply the existing Request Header Override configuration after task plugin
headers, including per-task, batch and realtime queries. Resolve api_key
from the credential used for that request and skip client-only placeholders
when polling has no original client request. Reuse the HTTP header resolver
without changing plugin contracts or adding provider-specific settings.

Keep polling redirects visible, matching submission and utils.fetch, so a
custom credential header cannot be forwarded to a redirect destination.
Document configuration and cover precedence, saved keys, absent client
context, invalid values, redirects and all polling initialization paths.

Validation with Go 1.26.4 and GOWORK=off:
- make test: root, relaykit and tokenkit passed before the realtime follow-up
- go test ./relay ./relay/channel ./relay/channel/task/jsplugin ./service -count=1: passed after the follow-up
- go vet ./... and go build ./...: passed after the follow-up
- Independent read-only review: passed

Scoped security review used OWASP ASVS 5.0.0 and the Authentication,
Session Management and REST Security cheat sheets. Credentials remain in
headers, client headers are not persisted, and redirects are not followed.
No live provider calls or production deployment were performed.
https://owasp.org/www-project-application-security-verification-standard/
https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
https://cheatsheetseries.owasp.org/cheatsheets/REST_Security_Cheat_Sheet.html

* fix(task): follow poll redirects and override content request headers

Polling follows redirects again through the shared HTTP client, which
checks every hop against the SSRF protection settings. Refusing them
turned each poll of a redirecting upstream into a failure, and after
enough failures the task was failed and refunded.

Credentialed content requests now carry the channel header overrides,
as submission and polling do. Credentialless links, utils.fetch and the
Host header stay as the plugin builds them.

* fix(task): settle tasks that a realtime query sees finish

Fetching a Gemini or Vertex task queries the upstream directly. When
that query saw the task finish, it wrote the final status itself, so
the poller skipped the task and never settled or refunded it: a failed
task kept its charge. Hand that transition to the poller's single-task
update, which records the status and settles exactly once.

---------
Co-authored-by: CaIon <[email protected]>
2026-10-20 20:50:02 +08:00
Seefs 603a5ceb55 feat(dashboard): show cache usage alongside total tokens (#7773)
* feat(dashboard): show cache usage alongside total tokens

* fix(dashboard): align cache stat descriptions and fix typecheck
2026-10-20 15:42:31 +08:00
邹云森森andCaIon 32e587406c feat(email): 添加 resend 邮件服务商接口 (#7880)
* 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.html
https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
https://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
[email protected], is rejected instead of breaking every send.
Resend's error text and network errors are quoted in the server log.

---------

Co-authored-by: CaIon <[email protected]>
2026-10-19 20:22:37 +08:00
mrlonelyandCalcium-Ion 25372e30b1 fix(relay): Claude 流式请求在准备和重试期间持续保活 (#7857)
* fix(relay): keep Claude streams alive during preparation and retries

* fix(relay): retain failed outcomes for keepalive stream errors

* fix(relay): wait one interval before sending keepalive

* fix(relay): synchronize request keepalive handoff before headers

Stop and join the request keepalive before the stream scanner copies upstream
headers. The scanner then owns subsequent ping and data writes through its
shared write mutex.

Add regression coverage for an immediate stream and for the first keepalive
committing response headers. Before the fix, the latter case reports a data
race between Header.Clone and copyCodexSSEHeaders.

Validation: GOWORK=off GOCACHE=/private/tmp/new-api-pr7857-review-go-cache go test -race -p 2 -parallel 1 ./controller ./relay/channel ./relay/helper -run 'TestRelayKeepAlive|TestRelayStreamErrorAfterKeepAlive|TestStreamKeepAlive|TestDoRequest|TestNewTaskAPIRequest|TestProcessHeaderOverride|TestStreamScannerHandler_' -count=1
Result: all three packages passed on Go 1.26.6, darwin/arm64.
git diff --check passed.

---------

Co-authored-by: Calcium-Ion <[email protected]>
2026-10-19 19:25:11 +08:00
石头磊 058c8231b6 fix(audit): add composite index for audit log ordering (#7874)
* fix(audit): index audit log ordering

* test(audit): isolate PostgreSQL migration checks
2026-10-18 23:46:10 +08:00
RedwindA 55bb6b903c fix(notify): keep notification limit window from resetting every hour (#7877)
* fix(notify): keep notification limit window from resetting every hour

The rate-limit key included the current clock hour, so the count reset on
every hour boundary and NOTIFICATION_LIMIT_DURATION_MINUTE above 60 had no
effect. Drop the hour suffix so the window runs for the configured duration
from the first notification, and make the Redis counter atomic with
SETNX+INCR in one transaction.

* fix(notify): fall back to default for nonpositive notification window

A zero or negative NOTIFICATION_LIMIT_DURATION_MINUTE left the Redis
counter without an expiry (or failed the SET), which blocked quota
notifications for good once the counter filled.

* test(notify): cover the Redis notification limit window
2026-10-18 22:43:50 +08:00
Calcium-Ion 6cf0d90b5f feat(deepseek): support V4.1 Flash, the Search and Fetch APIs, and @ reasoning modifiers (#7867)
* 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.
2026-10-17 23:24:33 +08:00
Calcium-Ion 6461f084ff feat(notify): turn each kind of notification on or off (#7868)
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.
2026-10-17 22:17:59 +08:00
CaIon 5c31c1fa9e feat(channels): improve priority dragging and add onboarding guides
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.
2026-10-17 21:52:39 +08:00
CaIon 5e9595190e feat(channels): add drag mode for channel priority ordering
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.
2026-10-17 20:19:41 +08:00
CaIon 5a59d69153 fix(pricing): hide task prices left at zero when another price is set
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".
2026-10-17 20:19:40 +08:00
feitianbubu 3e2ce2c885 fix: align model card summary with status bar hours (#7847) 2026-10-17 14:04:03 +08:00
RedwindA b0a98ac49c fix(web): apply theme class before first paint so Dark Reader detects dark mode (#7571)
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.
2026-10-17 14:03:48 +08:00
xuu1998andxuu1998 f92c576506 fix(web): cover Cloudflare clef and NVIDIA model families in provider rules (#7840)
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 <[email protected]>
2026-10-17 13:07:41 +08:00
sicnuyudidiandseefs001 ff3684658f fix(relayconvert): 保留 Chat/Claude → Responses 转换中 input_image 的 detail 字段 (#7855)
* fix(relayconvert): preserve input_image detail in Chat/Claude -> Responses conversion

* fix(relayconvert): preserve original image detail in Responses

Preserve all supported image detail levels during Chat to Responses conversion instead of downgrading original to auto.

Use bogus for unknown-detail fallback coverage and verify auto defaults for Claude message and tool-result images.

---------

Co-authored-by: seefs001 <[email protected]>
2026-10-17 12:58:08 +08:00
Alex Xiangandseefs001 09109ec315 fix(middleware): 过载哨兵改用容器口径并加入连续性与新鲜度判定 (#7317)
* fix(middleware): 过载哨兵改用容器口径并加入连续性与新鲜度判定

checkSystemPerformance 依赖 common.SystemStatus,而 updateSystemStatus 用
cpu.Percent(0,false) 与 mem.VirtualMemory() 采样,两者都是宿主机口径
(/proc/stat、/proc/meminfo),不具备 cgroup 感知。容器化部署下度量的是
宿主机而非本进程:容器配额 2 核时 /proc/stat 仍暴露宿主机全部核心,同节点
其它工作负载会把该值推高,导致正常 relay 流量被 503 system_cpu_overloaded
拒绝。同时该判定对单次采样即时生效,瞬时尖峰即可熔断。

本次调整:
- 新增 common/container_usage.go:优先按容器 cgroup 配额计算自身 CPU 与内存
  占用(v2 cpu.max/cpu.stat/memory.max/memory.current,兼容 v1),容器口径
  不可用时回退宿主机口径;
- 过载判定要求连续 3 次采样(约 15 秒)超阈值,任一采样回落即恢复;
- 采样超过 30 秒未刷新时哨兵不再熔断,避免状态陈旧误伤流量;
- 中间件改为消费采样结果中的过载标记,错误码与 503 语义不变。

验证:go build ./...、go test ./common/... ./middleware/... 通过;新增
容器口径与判定行为单测。

* fix(monitor): correct container CPU and memory overload detection

---------

Co-authored-by: seefs001 <[email protected]>
2026-10-16 22:30:06 +08:00
CaIon d032204733 feat(aws): add Mantle Messages API support
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
2026-10-16 14:45:17 +08:00
Roger Chien ca2cae676d fix(relaykit): emit summary on reasoning items announced without one (#7833)
The Responses schema requires summary on every reasoning item, but the
chat-to-Responses stream announces a reasoning item with an empty
summary that omitempty drops. Strict clients reject the item and then
see every summary event that follows as having no active item.

Items that already carry summary text keep their existing encoding.
2026-10-16 13:44:03 +08:00
CaIon 45fbb149f0 fix(rate-limit): count each group with its own limit separately
Group rate limits only picked the limit; every request was still
counted against the user as a whole. A user with 200 successful
requests in a group allowed 500 per minute got a 429 on the first
request in a group allowed 50, and in Redis one request in the smaller
group cut the larger group's remaining burst down to the smaller one.

A group that has its own limit now counts each user's requests on its
own, for both the total and the success limit. Groups without their own
limit still share the user's global limit, so tokens spread over
several groups do not multiply it.
2026-10-15 22:38:36 +08:00
RedwindA 6549a17eda fix(dashboard): draw flow chart labels in the theme text color (#7821)
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.
2026-10-15 18:00:20 +08:00
Seefs c9179d38b7 fix(playground): support the reasoning field in chat responses (#7831) 2026-10-15 17:59:00 +08:00
CaIon 908f94d65d fix(web): hide pages the user cannot open from console search
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.
2026-10-15 17:57:30 +08:00
RedwindA 19d435e7fd fix(relay): mask only the custom upstream address in error messages (#7822)
* fix(relay): mask only the upstream address in error messages

MaskSensitiveInfo masked every URL, domain and IPv4 in upstream error
messages, which garbled documentation links, email domains and dotted
identifiers that clients need to read.

It now takes the upstream addresses of the request: the channel base URL,
the origin the gateway actually dialed (recorded in doRequest, DoWssRequest,
the AWS SDK client and the Xunfei and Volcengine TTS dials, which covers
absolute Advanced Custom routes), the channel proxy and the environment
proxy. URLs and bare hostnames on those hosts and all IPv4 addresses are
masked; other URLs and domains stay readable. With no upstream address no
address is masked. Credentials are masked on every URL regardless of host:
api_key values, query values and userinfo.

The URL matcher also keeps quotes, colons and semicolons that follow a URL,
keeps parentheses and apostrophes inside it, masks an IPv4 URL host whole and
keeps a country-code TLD when the host has a port.

* fix(relay): keep preset official endpoints readable in masked errors

A channel on the base URL new-api presets for its type reaches a public
official endpoint, so its address is not masked. Only addresses the admin
configured are: a custom base URL, the origin of an Advanced Custom route,
and the channel and environment proxies. Preset hosts are compared with
their port so a configured localhost upstream stays apart from the Ollama
preset.

The origins recorded for the AWS SDK, Xunfei and Volcengine TTS dials and the
plugin allowedHosts masking are dropped: they are all official endpoints.

* fix(relay): mask IPv4 addresses and plugin allowed hosts in every error

IPv4 addresses in network errors include the gateway's own address, so they
are masked even when the channel uses a preset base URL. Task plugin build
failures mask the plugin's allowedHosts again: an installed plugin may point
them at private hosts.

* fix(relay): mask bracketed IPv6 addresses in errors

Go network errors write IPv6 addresses in brackets, as in
"dial tcp [2001:db8::1]:443". Like IPv4, they are the resolved upstream or
the gateway's own address, so they are masked on every error once the
bracketed content parses as an IP.
2026-10-15 15:49:45 +08:00
CaIon 718a4e7f97 fix(vertex): use the multi-region hostnames for us and eu
Vertex AI serves the us and eu multi-regions from
aiplatform.us.rep.googleapis.com and aiplatform.eu.rep.googleapis.com.
The URL builder turned every non-global location into
<location>-aiplatform.googleapis.com, so a channel whose deployment region
was us or eu sent requests to us-aiplatform.googleapis.com and
eu-aiplatform.googleapis.com. Single regions and global are unchanged,
and a custom base URL still decides the host.
2026-10-14 23:20:54 +08:00
CaIon f196934ce6 feat(plugins): bind Hailuo, Vidu and Veo plugins to New API channels
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.
2026-10-14 23:20:54 +08:00
CaIon bfdad293cd fix(vidu): bill the model, duration and resolution Vidu generates
buildSubmitRequest spread metadata over the request after the billed
fields were computed, so a model, duration or resolution inside metadata
reached Vidu while the charge used the top-level values: a request billed
as vidu1.5 for 4 seconds at 360p could generate viduq2 for 8 seconds at
1080p. The billed fields are now applied last; metadata still carries
every other Vidu field.
2026-10-14 23:20:54 +08:00
CaIon 150ab3cd43 chore(licenses): refresh the third-party dependency inventory
The table had drifted from go.mod, web/bun.lock and electron/package-lock.json. Versions now match what the builds ship, missing direct dependencies are listed, testify moves to test scope, and go-sql-driver/mysql's MPL-2.0 license gets a section.

go-epay is listed under MIT, the license it ships with, so the Proprietary/Internal section has no dependency left and is removed.
2026-10-14 23:20:54 +08:00
CaIon 76cd5114ea build: upgrade moejs to v0.1.0-alpha.7
alpha.7 is a performance release on the JSON and call path: JSON.stringify
and AppendJSON keep their stringifier on the stack, JSON.parse clears its
working stack once per parse, ReleaseCallData clears only the registers a
finished call used, and arithmetic and bitwise results reach registers
without a write barrier. The interpreter's register stack now grows in
place within its capacity.

The host only calls ReleaseCallData, so no adaptor changes are needed.
new-api builds and its plugin tests pass unchanged.
2026-10-14 23:20:54 +08:00
EtgpaoandCaIon 1b63c28dcc fix(claude): preserve explicit zero usage and cache-only billing (#7816)
* fix(claude): preserve explicit zero usage during stream finalization

* test: use synthetic values for Claude usage regressions

* fix(claude): estimate stream usage only when the counts are missing

Trusting every explicit zero let an upstream that reports zeros stream
output for free, and a zero in message_delta erased the input counted
at message_start. Decide from the counts instead: input is missing only
when input, cache reads and cache writes are all zero, and output only
when it is zero after streamed content. A broken stream still takes the
larger of the reported and estimated output. A fully cached prompt and
a refusal before any output keep their zeros, so relaykit usage parsing
goes back to its previous form.

---------

Co-authored-by: CaIon <[email protected]>
2026-10-14 22:31:33 +08:00
feitianbubu c9f7b62081 fix(web): sort pricing models by input, output and per-request price (#7813) 2026-10-14 21:36:19 +08:00
枭钰钰 a0bf6a8a41 fix(vertex): pass project_id in API Key mode to avoid 404 on official endpoints (#6330)
* 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.
2026-10-14 20:16:30 +08:00
Seefs 4013729f6a fix: disable sensitive word filtering by default and clear the default word list (#7814) 2026-10-14 17:55:42 +08:00
CaIon 7de10f2c60 feat(token): let an API key read its owner's quota
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
2026-10-13 21:39:52 +08:00
CaIon ef60ef27f3 fix(relay): keep the cause of a failed body read for every later reader
A failed read consumes and closes the request body. Endpoints that read
it a second time then answered "http: invalid Read on closed Body"
instead of the cause: /v1/video/generations, and audio transcriptions
and translations. The first failure is now kept for the rest of the
request, so every later reader reports it.

A body over MAX_REQUEST_BODY_MB now gets 413 instead of 400 on the
endpoints that still answered 400: ordinary relay endpoints such as
/v1/chat/completions, Midjourney, task plugin routes and /v1/tasks.
Task plugin routes also write the cause to the log, and Midjourney
errors no longer repeat "Invalid request:".
2026-10-13 19:48:01 +08:00
CaIon d10eafb57e fix(relay): report the real cause when a request body cannot be read
POST /v1/responses, /v1/videos and /v1/images/* pass through the task
plugin endpoint check before any model is matched. A body that failed to
read or parse there got 400 "Invalid task protocol request" even when no
plugin claims the model, and the log carried only that sentence, so the
real cause, such as a body over MAX_REQUEST_BODY_MB or a connection cut
mid-upload, was lost.

These requests now get the cause in the response and in the log, and a
body over the size limit gets 413 with the limit in the message. Other
endpoints such as /v1/chat/completions no longer repeat the prefix, as in
"Invalid request: Invalid request: invalid JSON request body".
2026-10-13 19:48:01 +08:00
Jack C. 63cecab824 feat(channel): support Sub2API balance queries (#7749) 2026-10-13 12:36:43 +08:00
Seefs b4be2a7847 feat(ui): show newly added pricing groups at the top (#7782) 2026-10-13 12:35:05 +08:00
Calcium-Ion 633b4c5b19 feat(logs): export usage logs from the common log page (#7774)
* 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.
2026-10-12 21:33:53 +08:00
dependabot[bot] dd59854050 chore(deps-dev): bump undici from 6.28.0 to 6.29.0 in /electron (#7624)
Bumps [undici](https://github.com/nodejs/undici) from 6.28.0 to 6.29.0.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.28.0...v6.29.0)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 6.29.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-12 21:03:04 +08:00
dependabot[bot] 93cf4ba6fb chore(deps-dev): bump js-yaml from 4.3.1 to 4.3.2 in /electron (#7327)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.3.1 to 4.3.2.
- [Changelog](https://github.com/nodeca/js-yaml/blob/4.3.2/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.3.1...4.3.2)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.3.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-12 20:49:10 +08:00