Commit Graph

1119 Commits

Author SHA1 Message Date
decolua
9dbdca0e5e refactor(search): fold zai-search into the glm provider
The separate zai-search entry showed "No connections" on the web search
page because credentials live on the `glm` connection, not on it. Every
other provider that does both chat and search (antigravity, kimi, xai,
gemini) declares webSearch on the provider itself, so do the same here.

- glm gains serviceKinds ["llm", "webSearch"] and the MCP searchConfig
- the request builder / normalizer move from "zai-search" to "glm"
- drop the zai-search registry entry and its svg logo, which also
  removes the only need for svg logo support in getProviderIconSrc

ollama-search keeps its own entry and credentialFallback: its search
endpoint is unrelated to the ollama chat transport.
2026-08-28 16:12:04 +07:00
decolua
5a86f6a8d2 feat(search): add ollama-search and zai-search with credential fallback
Register two web search providers that reuse an existing chat provider's
API key instead of requiring their own connection:

- ollama-search (POST ollama.com/api/web_search) reuses the `ollama` key
- zai-search (POST api.z.ai MCP web_search_prime) reuses the `glm` key

A new `credentialFallback` registry field drives this: when a search
provider has no connection of its own, the search handler falls back to
the linked chat provider's credentials.

Also teach getProviderIconSrc to serve .svg logos for providers that
ship vector art.
2026-08-28 16:04:45 +07:00
huohua-dev
eb312bd470 fix(claude): decloak tool names in same-format streaming passthrough
translateResponse() short-circuited untouched on claude->claude streaming,
so OAuth-cloaked tool names (CLAUDE_TOOL_SUFFIX) leaked to the client and
every tool call was rejected as unknown. Add decloakStreamChunk(), the
streaming counterpart of decloakToolNames(), and call it on the same-format
path using the already-plumbed state.toolNameMap.
2026-08-28 15:40:40 +07:00
Daniel Gonçalves Araujo
fcfcced4ab fix(usage): support CREDIT_LIMIT and multi-interval GLM quotas
GLM quota parsing only accepted TOKENS_LIMIT and wrote every limit to a
single "session" key, so credit-based plans showed nothing and later
intervals overwrote earlier ones. Accept CREDIT_LIMIT too and derive the
quota key from the limit unit (5h session, 7d weekly, tokens, custom).
Moves the parser into its own usage/glm.js, re-exported from misc.js.
2026-08-28 15:35:23 +07:00
qingyong
56a40765e9 fix(translator): zai thinkingFormat sends reasoning.effort object
Z.ai / GLM-5.2+ require a top-level reasoning_effort (low/high/max)
alongside thinking:{type:"enabled"} to control reasoning depth; the zai
branch previously only set thinking and dropped reasoning_effort, so every
GLM-5.x request ran at the model default (max). Gate the field behind
GLM-5.2+ (thinkingEffortSupported in capabilities.js) since older GLM
(4.x, 5.0, 5.1, 5-turbo, 5v-turbo) do not read it, and map client levels
to the exact low/high/max values z.ai accepts.

extractThinking now checks reasoning_effort/reasoning.effort before the
thinking object so a client-supplied effort is not overwritten by
thinking:{type:"enabled"} mapping to mode:auto.

Fixes #2721
2026-08-28 12:32:41 +07:00
KunN-21
cadef6c4ff fix(rtk): make system prompt injection format-safe and idempotent
Caveman/Ponytail injection now matches each target wire format instead of
assuming an OpenAI-shaped body:

- Chat arrays append a text block; Responses arrays append input_text and
  create typed message items
- Claude inserts before the final cache-control block; Gemini preserves the
  snake/camel systemInstruction wrapper
- Kiro updates systemPrompt and its mirrored first-user prefix atomically,
  rolling back if the pair fails to converge
- Format label decides Claude/Gemini before the wire-shape sniff, since their
  bodies also carry messages[]/contents[] and Anthropic rejects a "system"
  role inside messages[]
- Delimiter-aware dedup makes injection exact-idempotent across retries, so
  distinct prompts sharing a long prefix are no longer collapsed
- Every write is fail-open on frozen or proxied bodies

Saver order and X-9Router-Token-Saver: off behavior are unchanged.

Fixes #3202.
2026-08-28 11:47:56 +07:00
anojndr
ab044e6d6d fix(opencode): route Muse Spark through the Responses API
muse-spark-1.2-contributor-free returned HTTP 500 on /zen/v1/chat/completions.
The model is only served by /zen/v1/responses, so route it there via a per-model
targetFormat and normalize the Chat fields the Responses API rejects
(max_tokens -> max_output_tokens, reasoning_effort -> reasoning{effort,summary}),
clamping max/ultra down to the highest effort the model accepts (xhigh).

Routing stays per-model: the other free models (big-pickle, hy3-free, mimo,
nemotron, laguna) are not served by /responses and keep /chat/completions.
2026-08-28 11:33:14 +07:00
decolua
14401c433c fix(ui): wait for the icon font itself before revealing Material Symbols
`document.fonts.ready` resolved before the 4MB Material Symbols woff2 even
started loading — it runs in <head>, ahead of any element that would trigger
the lazy fetch. The `fonts-loaded` class landed early, so icons rendered
blank until a second load served the font from disk cache.

Load the face explicitly and swap visibility for opacity, with a 3s fallback
so icons never stay hidden if the font fails.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 11:32:28 +07:00
warelik
e08ac6dada fix(tools): default Claude tool type when missing
Strict Anthropic-compatible gateways (e.g. MiniMax) reject Claude-format
requests with HTTP 400 when tools[].type is missing. Normalize each
missing/falsy tools[].type to "custom" before dispatch when the final
request format is Claude. Built-in tool types (computer_use, bash,
web_search_*) are passed through untouched.
2026-08-28 11:16:44 +07:00
Nguyen Thanh Dat
f9d82c6575 fix(stream): parse the trailing NDJSON line an Ollama stream leaves behind
createSSEStream splits on "\n" and keeps the remainder, which only flush()
parses. That call omitted targetFormat, so parseSSELine required a "data: "
prefix and dropped whatever an NDJSON provider left without a closing
newline. The !parsed.done guard compounded it: the SSE sentinel and an
Ollama final chunk both carry done:true, but the latter is the real last
chunk holding done_reason and the token counts.

Pass targetFormat and scope the sentinel check to formats that emit one, so
the tail reaches the translator. Accumulate its usage into state the same way
the transform loop does, so finalizeStream logs those tokens instead of null.
2026-08-27 20:53:02 +07:00
decolua
2f17352cc2 feat(search): add Antigravity as a web search provider
Route POST /v1/search with provider "antigravity" through Google Search
grounding on v1internal:generateContent, using the existing Antigravity
OAuth account pool. Grounding chunks become citations with the grounded
sentence as snippet and its surrounding answer text as content.

Upstream repeats a source across chunks, so citations are keyed by URL
and their snippets merged. A missing projectId is reported up front —
upstream answers a fabricated or absent project with a misleading
"no valid license" 403.

Based on the approach in #3437 by @Nautilaceae.
2026-08-27 20:48:43 +07:00
vianhanif
90a0005845 fix(cli): install better-sqlite3 without build tools on Node 22+
The runtime hook pinned better-sqlite3 12.6.2, whose prebuilds stop at
Node ABI 141 — on Node 26 the install fell back to a node-gyp source
build and failed on machines without build tools, silently degrading to
the sql.js fallback.

Node >= 22 now installs 13.0.3, which is N-API and ships per-platform
prebuilds inside the package. Two things were needed to make that
actually work:

- npm injects an implicit `node-gyp rebuild` for any package shipping a
  binding.gyp, so the install still demanded build tools; `--ignore-scripts`
  skips it and uses the bundled prebuild as-is.
- the binary check only looked at build/Release, which 13.x no longer
  creates, so every start re-ran npm install; it now also accepts
  prebuilds/<platform>-<arch>.node.

Node < 22 stays on 12.6.2 (13.x requires Node >= 22), and an existing
working install is left untouched either way.
2026-08-27 20:28:26 +07:00
Fábio A.
e79ae6e7c5 i18n(pt-BR): expand translation to 1132 terms
Add 144 missing pt-BR strings covering Usage, Endpoint & Key security
notices, 9Remote, Media Providers, Proxy Pools, Combo & Vision Adapter,
Token Saver, Agent Skills and Quota Tracker.
2026-08-27 20:06:10 +07:00
decolua
a68ada1c83 feat(cli-tools): share endpoint presets across every tool card
Each card kept its own copy of the localStorage preset logic inside
BaseUrlSelect, so an endpoint saved on one card was invisible to the
others until a reload, and a URL typed into the custom field was
forgotten the moment the card collapsed.

Move the store into cliEndpointPresets.js and publish a change event
so open cards resync live. Applying settings now remembers the
endpoint unless it matches a built-in option, and each card passes
its configured URL as currentUrl so BaseUrlSelect can preselect the
matching preset instead of always falling back to 127.0.0.1.
Deleting a preset falls back to the first real option rather than
clearing the field.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 18:54:44 +07:00
decolua
c4af43faa3 fix(stream): stop logging a disconnect for every completed Responses call
Responses-API clients (codex, droid) close the socket on
response.completed because the protocol has no [DONE] sentinel, so
every successful request printed " DISCONNECT: ResponseAborted"
after its own "📊 done" line. Keep the dbg("CTRL", …) trace and drop
the console line; ABORTED and ERROR still print.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 18:52:50 +07:00
decolua
d7f7d70dd5 fix(stream): record usage when a client closes on the terminal event
The Responses API has no [DONE] sentinel, so codex closes the socket
as soon as response.completed arrives. That cancels the reader before
flush() runs — and flush() held every usage side effect, so a fully
successful request logged nothing: no 📊 done line, no token stats,
no request detail.

Extract that tail into a once-guarded finalizeStream() and also call
it right after the terminal event is forwarded, in both passthrough
and translate mode. flush() still calls it; the guard makes the
second call a no-op. Streams that end normally are unaffected, and a
terminal event carrying no usage falls through to the existing
estimate/null path rather than blocking.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 18:52:42 +07:00
decolua
9c45b27cd7 fix(cli-tools): send the API key Codex actually reads
Codex only authenticates a custom model provider from env_key,
http_headers, env_http_headers or a token command — auth.json is
read solely by the built-in openai provider. Writing OPENAI_API_KEY
there left every request unauthenticated (401 Missing API key) while
clobbering an existing ChatGPT login.

Put the key in [model_providers.9router.http_headers] instead, and
drop the auth.json write. Also move the subagent model to the
agents.default_subagent_model scalar: agents.<role> now declares a
custom role and requires a description, so the old [agents.subagent]
table was discarded with a startup warning. DELETE still clears
auth.json to repair machines configured by the previous version.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 18:52:32 +07:00
decolua
e6f5724b4b fix(models): stop the catalog sync from erasing its own output
collectEntries() computed each model's "current" capabilities with the
previous catalog still installed, so every delta was measured against the
last one. An upstream value that still agreed with what we had written
looked like no change and was dropped: the second run cut `providers`
from 20 entries to 5, taking glm-5.3's 1M context correction with it.

The baseline has to be the hand-written tables alone, so the reader is
detached for the snapshot and restored in a finally — a mid-sync failure
must not leave capabilities.js without it.

Two smaller corrections:

- One vote per provider in the modality tally. Ids that normalize to the
  same model (claude-opus-4-thinking:1024, :8192, :32768 …) were each
  counted, giving nano-gpt five votes where other gateways had one. No
  model's result actually flipped — the variants agree with each other —
  but the majority rule only means something if the denominator does.
- Restore the etag from the file on startup. It lived only in module
  state, so every restart re-downloaded 4.3MB to be told nothing changed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 18:44:30 +07:00
decolua
d01724556a fix(models): drop the worker thread from the catalog sync
The worker resolved its own path through import.meta.url, which the
bundler rewrites — so the running server looked for the file at a path
that does not exist there:

  [modelCatalog] sync failed: Cannot find module
  '/Users/Working/router4/9router/src/lib/modelCatalog/worker.js'

It was guarding against a 23ms JSON.parse that runs once a day, 60s after
boot. Inlining it into sync.js costs that 23ms on an otherwise idle tick
and removes both the failure mode and a whole file.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 18:39:44 +07:00
DarRahman
40eed18688 feat(usage): track GPT-5.3-Codex-Spark quota windows
Extract Spark rate limit windows from the Codex usage response and expose
them as spark_session/spark_weekly quotas, reusing the existing prefix
mechanism. Map codex quota types to readable dashboard labels.

Fixes #3431
2026-08-27 17:56:26 +07:00
decolua
0532f00d84 feat(models): refresh model capabilities from models.dev in the background
Capability tables are hand-maintained, so a model gains vision or a wider
context only when someone notices and edits the file. This adds a daily
sync that fills the gap for models already in the registry.

How it decides:

- Modalities (vision/pdf/audio/video) belong to the MODEL — every gateway
  serving glm-5.3-flash serves the same weights — so they are keyed by
  model id and shared. A majority of sources must declare one, which keeps
  out lone mis-declarations: minimax-m2.5 (1 of 45), glm-4.7 (1 of 44) and
  gpt-oss-120b (2 of 76) are text-only despite a reseller claiming vision.
- Context/output limits belong to the GATEWAY — each truncates differently
  (glm-5 ships as 202752/16384 on one host and 204800/131072 on another) —
  so they are keyed by provider + model and only the matching provider's
  own numbers are trusted.

Both layers are strictly additive and sit BELOW the hand-written tables,
which short-circuit first. A capability already true stays true.

Mechanics: worker thread (the 4MB parse would block the loop ~20ms),
ETag so an unchanged catalog costs one empty request, 60s startup delay,
30min backoff on failure, MODEL_CATALOG_SYNC=off to disable. Only the
~57KB delta is kept; lookups cost ~0.1us via an mtime-guarded cache.

capabilities.js is bundled into the browser through useModelCaps, so it
cannot import node:fs — the server injects the reader via
setCatalogSource() from instrumentation.

visionPatterns.js is the last resort: a model nobody has catalogued yet
still accepts images when its id says so (qwen3-vl-plus, glm-4.6v, llava),
with image-generation and embedding ids excluded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 17:53:53 +07:00
decolua
9c650e1d54 feat(models): add GLM-5.3-Flash, DeepSeek V4 Vision, Grok 4.5/4.6
Vendors shipped four multimodal models the registry did not carry:

- glm-5.3-flash — z.ai's first natively multimodal GLM-5, 1M context,
  image + video + pdf input (glm, glm-cn, opencode-go)
- deepseek-v4-flash-vision-exp — image input at V4-Flash text parity,
  1M context / 384k output (deepseek, opencode-go)
- grok-4.6, grok-4.5 — 500k context; 4.6 has no text output limit (xai)

Capabilities needed hand entries because the existing globs mis-matched:
*glm-5* and *deepseek-v4* carry no vision, and *grok-4* would have capped
grok-4.6 at 256k instead of 500k. The grok-4.6 pattern sits above the
generic *grok-4* so it wins the first-match lookup.

Also corrects glm-4.6v / glm-4.5v, which were missing video input and
declared no maxOutput, and backfills glm-4.6v on glm-cn — zhipuai serves
it and the sibling provider already listed it.

tests/unit/opencode-go-models.test.js pins the opencode-go model list, so
its expected array moves with the registry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 17:52:01 +07:00
Nim
1a3db1efae feat(antigravity): quota-aware routing with reset-aware fallback
On a 409/429 from Antigravity, fetch live quota to learn the exact
per-model resetAt instead of guessing a backoff, then skip only the
exhausted account/model pair until that time.

- antigravityQuota.js: in-memory quota cache, coalesced concurrent
  refreshes, 30s throttle per connection (applied to failures too),
  keeps known cache when upstream returns 401/403 error payloads
- auth.js: pre-filter exhausted account/model pairs; report the
  earliest quota reset when every account is blocked; skip the
  30-minute cooldown cap so the upstream resetAt is not truncated
- chat.js: antigravity 409/429 falls back on the RAM cache only, no
  persistent modelLock_* for this path
- Logs identify accounts by id prefix, never email or name

Closes #3561
2026-08-27 16:18:08 +07:00
kriptoburak
f0a6d35818 feat(search): add Xquik as an X search provider
Xquik needs a GET request with x-api-key auth and a tweets envelope
normalizer, neither of which the generic search fallback provides. Adds a
dedicated request builder and normalizer, cursor pagination passthrough,
result-based credit usage reporting, and a validateUrl probe so key
validation hits the no-charge credits endpoint.
2026-08-27 16:10:00 +07:00
vianhanif
548e32aacf fix(rtk): set diagnostic before silent null return on Responses translation failure
The openai-responses branch of compressWithHeadroom returned null without
recording a reason, leaving the diagnostics panel blank and making Codex
translation failures indistinguishable from a successful compression.
2026-08-27 15:52:46 +07:00
ariesho2903
abb20d9f39 feat(dashboard): bulk import Grok CLI accounts from JSON
Add a "Bulk Add" flow for the grok-cli provider, mirroring the existing
codex one: paste a JSON array/object or drag-drop multiple .json files,
then create all OAuth connections in a single call.

- BulkImportGrokCliModal: flexible JSON parsing (array, single object,
  {accounts:[...]}, concatenated objects) + multi-file upload
- POST /api/oauth/grok-cli/bulk-import: serial createProviderConnection,
  snake_case/camelCase token fields, email backfilled from id_token or
  access_token, authMethod "device_code" to match the login flow
2026-08-27 15:49:35 +07:00
decolua
699edac327 # v0.5.55 (2026-08-14)
## Features
- **Auth**: native SAML 2.0 SSO alongside OIDC — AuthnRequest generation, ACS
  assertion handling, SP metadata export, admin config test, replay-protected
  via a `saml_state` cookie matched against `InResponseTo`
- **Providers**: add Alibaba Token Plan (`token-plan.ap-southeast-1`) — the
  fourth Alibaba key type, Singapore-only and OpenAI-compatible transport only
- **Providers**: add `glm-5.3` to GLM Coding and GLM (China)
- **Providers**: Kimchi accepts API keys as well as OAuth (dual auth), with a
  working Test Connection for both modes
- **Antigravity**: add Gemini 3.7 Flash and its tiered high/medium/low variants
  (also in the Gemini registry) with pricing and quota tracking
- **TTS**: add Fish Audio — model id travels in an HTTP `model` header, voice
  is a `reference_id` (preset or cloned voice model)
- **OpenCode-Go**: route by request format via declared transports instead of
  forcing every client into `/messages` — Codex/OpenAI clients no longer pay a
  lossy Responses→OpenAI→Claude double translation. Per-model `supportedFormats`
  guard; the bespoke executor is gone (its shared `_lastModel` cache could cross
  auth headers between concurrent requests)
- **Usage**: dedup + cache Claude quota calls (120s TTL keyed by access token,
  in-flight promise dedup, last-good read on soft failure) to stop multiple
  tabs tripping 429; manual refresh (↻) sends `force=1` to bypass the cache

## Fixes
- **Docker**: ship `sql.js` in the image so the pure-JS DB fallback can start —
  file tracing carried the package's JS without `dist/sql-wasm.wasm`, so a
  container with no native driver aborted with ENOENT and never got a database
  (#3248)
- **Usage**: read Gemini `usageMetadata` out of the antigravity `{ response }`
  envelope — every non-streaming antigravity request logged `IN 0 | OUT 0`
  (#3260)
- **Claude**: re-anchor passthrough cache breakpoints — the client's own
  `cache_control` markers point at pre-normalization offsets, so the tail was
  re-cached every request. Last system block and last tool pinned at 1h TTL,
  last assistant turn at 5m, mid-conversation system messages folded into the
  neighbouring user turn instead of hoisted into `body.system`
- **Combos**: detect images from Hermes and attachment payloads (`images[]`,
  `experimental_attachments`, message-level `image_url`/`audio_url`, inline
  `data:` URIs) so the Vision Adapter auto-switch fires for Hermes/Ollama/
  Vercel AI SDK shapes
- **Kiro**: intercept chat via `x-amz-target` — Kiro IDE 1.0.228+ moved
  `GenerateAssistantResponse` to `POST /` + header, bypassing MITM. Also emit
  the now-mandatory initial-response frame and map the `auto` model slot
- **Kiro**: report real output tokens and stop discarding usable turns
- **Qoder**: detect billing blocks at stream start and return a synthetic 403
  so combo/account fallback triggers instead of leaking the error into chat
- **Antigravity**: strip competitive system prompts (Zed IDE's Claude-agent
  prompt) that Antigravity flags with a 429 Quota Exhausted
- **OpenCode**: send the official client fingerprint on free-tier requests so
  the Console stops classifying traffic as unidentified and rate-limiting it;
  session id resolves conversation-stable to preserve prompt caching
- **Responses**: don't close the message on an empty `tool_calls` array — some
  providers attach one to every chunk, and the truthy check ended the message
  on the first content token (#3234)
- **Translator**: preserve `prompt_cache_key` when converting chat to responses
- **Models**: expose snake_case token limits on `/v1/models`
- **Combos**: strip `stream_options` from the Fusion panel fan-out to avoid a
  DeepSeek 400 (#3024); raise the dashboard model-test probe budget to 1024 and
  soft-pass reasoning-only responses (#3010)
- **Headroom**: the toggle reflects the `headroomEnabled` setting even when the
  proxy is down — it previously showed OFF while the engine kept calling
  `/v1/compress`; proxy status stays visible via the status chip
- **Hermes**: add the `api_key` parameter to the model block in YAML config
- **Providers**: add llm7 to provider test support

## Docs
- **i18n**: add Spanish, French, and Brazilian Portuguese README translations

## Security
- **Real IP**: `x-9r-real-ip` and the Host fallback were trusted from
  client-controlled headers whenever `custom-server.js` was not in the request
  path (`npm run start`, `start:bun`), letting a remote caller pose as local to
  skip API key auth and reach `LOCAL_ONLY_PATHS` (`/api/mcp/*`,
  `/api/tunnel/enable`, `/api/auth/reset-password`). The server now stamps a
  per-process `x-9r-peer-token` on every request it sanitizes and only trusts
  `x-9r-real-ip` behind it — falling back to Host in development and failing
  closed in production (GHSA-pjm4-8fpg-f9p6). Also fixes IPv6 loopback
  detection (`::1`, `::ffff:127.0.0.1`) and routes `npm run start` /
  `start:bun` through `custom-server.js`
- **Search**: `resolveBaseUrl()` rejects client-supplied non-public baseUrls
  (SSRF guard on `/v1/search`)
- **Login**: fresh-install remote login with the default password returns 403
  without issuing a JWT
- **Usage**: `/api/usage/request-details` redacts request/response payloads
2026-08-14 17:08:02 +07:00
decolua
540ebbe682 test(baseline): regenerate provider snapshot for opencode-go transports 2026-08-14 16:53:04 +07:00
KiMelody
e1115e2839 feat(opencode-go): route by request format via transports + per-model guard
opencode-go hard-coded targetFormat: claude per model, so every client
format was force-routed to /messages (Codex/OpenAI clients paid a lossy
Responses->OpenAI->Claude double translation). Declare the existing
upstream multi-endpoint transports [openai, claude, openai-responses]
and guard per model via registry supportedFormats: kimi/glm/mimo only
support /chat/completions, minimax/qwen add /messages, deepseek adds
/responses. Undeclared models keep the upstream default.

Drop the bespoke OpenCodeGoExecutor (its shared _lastModel cache could
cross auth headers between concurrent requests); DefaultExecutor already
consumes runtimeTransport and injects reasoning content.
2026-08-14 16:52:37 +07:00
Nguyen Thanh Dat
27f3710c8b fix(docker): ship sql.js so the pure-JS DB fallback can start
Next file tracing follows JS imports, and sql.js loads dist/sql-wasm.wasm by
path at runtime, so the standalone output carries the package's JS without its
wasm binary. When both native drivers fail the last-resort adapter then aborts
with ENOENT on the missing binary and the container never gets a database.

The CLI bundle already guards this explicitly (build-cli.js step 3b,
ensureModuleInBundle("sql.js")); the image just never got the same treatment.
Copy the package the same way node-forge and next already are.

Fixes #3248
2026-08-14 16:40:53 +07:00
Nguyen Thanh Dat
59d858b639 fix(usage): read Gemini usageMetadata out of the antigravity response envelope
Antigravity and gemini-cli wrap their payload in { response: {...} }.
extractUsageFromResponse only tested top-level usageMetadata, so every
non-streaming antigravity request logged zero usage (IN 0 | OUT 0) and
zeroed rows in the usage dashboard. Read the envelope the same way
usageTracking.js and nonStreamingHandler.js already do; top-level
metadata keeps priority and the OpenAI/Claude branches are untouched.

Fixes #3260
2026-08-14 16:34:54 +07:00
Nguyen Thanh Dat
92259214db fix(security): require proof that x-9r-real-ip came from the socket (GHSA-pjm4-8fpg-f9p6)
x-9r-real-ip and the Host fallback were trusted from client-controlled
headers whenever custom-server.js was not in the request path (npm run
start, start:bun), letting a remote caller pose as local to skip API key
auth and reach LOCAL_ONLY_PATHS (/api/mcp/*, /api/tunnel/enable,
/api/auth/reset-password).

custom-server.js now generates a per-process secret at boot and stamps it
as x-9r-peer-token on every request it sanitizes. hasTrustedPeerHeaders()
(src/lib/auth/trustedPeer.js) gates trust in x-9r-real-ip on that secret;
otherwise the guard falls back to Host only in development, and fails
closed in production. Same gate on loginLimiter.getClientIp() so a spoofed
header cannot rotate the login lockout bucket.

Also: fix isLoopbackHostname for IPv6 (::1, ::ffff:127.0.0.1) which the
old split(":")[0] reduced to empty string; route npm run start /
start:bun through custom-server.js (postbuild copies it into
.next/standalone, build-cli.js fails without it) so documented deployments
keep passwordless local access.
2026-08-14 16:33:58 +07:00
Nguyen Thanh Dat
b04c03c6b5 feat(providers): add Alibaba Token Plan (token-plan.ap-southeast-1)
Fourth Alibaba key type — Coding Plan (alicode/alicode-intl) and Model Studio
(alims-intl) both reject Token Plan keys. Registry entry only; PROVIDER_MODELS
builds from providers/registry so no executor or translator work is needed.

Singapore-only (eu-central-1 answers IllegalEndpoint) and OpenAI-compatible
transport only (the Anthropic surface is not authorized for this plan).

Closes #2754
Closes #2806
2026-08-14 16:32:54 +07:00
AlexNoVibe
8b2b2fefb5 docs(i18n): add Spanish and French README translations
Add i18n/README.es.md and i18n/README.fr.md mirroring the English
README structure, and link both from the language switcher.
2026-08-14 16:29:43 +07:00
Azriel Akbar Ferry Ardiansyah Kusumawardhana
86694ed8d0 feat(antigravity): add Gemini 3.7 Flash models (#3286, #3281)
Add gemini-3.7-flash and its tiered high/medium/low variants to the
Antigravity and Gemini registries, with matching capabilities, pricing
and Antigravity quota tracking.

extractModel now recognises gemini-3.7-flash-tiered alongside 3.6 and
derives the version from the request, so thinkingLevel still maps to the
right tiered alias.

Closes #3286
Closes #3281
2026-08-14 16:27:10 +07:00
Nguyen Thanh Dat
8af5e752da feat(tts): add Fish Audio as a text-to-speech provider
Registry entry plus one config-driven FORMAT_HANDLERS handler. The model id
travels in an HTTP `model` header rather than the JSON body, and the voice is
a reference_id (preset or cloned voice model).

Closes #2411
2026-08-14 16:21:11 +07:00
zmf
8ed9da7165 feat(providers): add glm-5.3 to GLM Coding and GLM (China) registries
Zhipu released GLM-5.3 on both api.z.ai and open.bigmodel.cn coding
endpoints. Verified live against both, returning model:"glm-5.3" with
native reasoning_content.

No other changes needed: the '*glm-5*' family pattern in capabilities.js
and 'glm-5*' in pricing.js already cover it.
2026-08-14 16:16:46 +07:00
decolua
7e5f5a8813 fix(claude): re-anchor passthrough cache breakpoints with 1h TTL
Passthrough kept the client's own cache_control markers, which point at
pre-normalization offsets. Once normalize/dedupe reshaped system and tools,
the breakpoints landed mid-array and the tail was re-cached every request.

- Pin the last system block and last tool at ttl 1h (was the client's 5m)
- Anchor the last assistant turn at 5m, falling back to the final message
  so a first turn still gets a breakpoint
- Fold mid-conversation system messages into the neighbouring user turn
  instead of hoisting them into body.system, where the volatile token
  counters invalidated the prefix on every request
- Run the anchoring after every token saver, at the final body

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 16:08:30 +07:00
Bertho Joris
345cdcf6a5 fix(combo): detect images from Hermes and attachment payloads for Vision Adapter
Inspect images[], experimental_attachments/attachments, message-level
image/image_url/audio_url, and inline data:image|audio|pdf URIs on trailing
user turns so Vision Adapter auto-switch fires for Hermes/Ollama/Vercel AI
SDK shapes. stripOpenAI now also drops msg.images and image attachments when
the active model lacks vision support.
2026-08-13 18:30:44 +07:00
Duc Nguyen
65197ad11c feat(auth): add native SAML 2.0 SSO integration
Add SAML 2.0 as a second SSO protocol alongside OIDC under a unified
authMode/ssoType model. SP flows via @node-saml/node-saml: AuthnRequest
generation, ACS POST assertion handling, SP metadata export, and admin
config test endpoint. Replay-protected via saml_state cookie (httpOnly,
SameSite=Lax) matched against InResponseTo; wantAssertionsSigned enforced.

- src/lib/auth/saml.js: SAML instance builder, X.509 cert formatter, claim pickers
- 4 routes under src/app/api/auth/saml/: start, acs, metadata, test
- settingsRepo: ssoType + saml* defaults; login/status routes dispatch by type
- profile page: SSO protocol switcher, IdP metadata XML + cert uploaders
- login page: dynamic SAML sign-in button; Header: SAML user badge
2026-08-13 17:56:34 +07:00
Fadjrir Herlambang
e02bde4a70 feat(providers): add Kimchi API key support (dual OAuth + API key)
Kimchi's transport is OpenAI-compatible (Authorization: Bearer) but the
registry declared it OAuth-only, so the dashboard, /api/providers, and
the connection test all rejected API keys. Enable dual auth
(authModes: ["oauth", "apikey"]) and add a kimchi case to
testApiKeyConnection so the Test Connection button works for both modes.
Regenerate the golden snapshot with the Kimchi entries (+ other
previously-missing providers).
2026-08-13 17:53:44 +07:00
haumanto
30fec4318e fix(models): expose snake_case token limits on /v1/models 2026-08-13 12:19:06 +07:00
CyrixJD115
67271d859e fix(opencode): send official client headers on free-tier requests
Mirror the official opencode CLI fingerprint (User-Agent, x-opencode-session, x-opencode-request, x-opencode-project) on free-tier requests so the Console no longer classifies traffic as an unidentified client and rate-limits it with FreeUsageLimitError / HTTP 429.

Session id resolves conversation-stable via resolveSessionId (client session to assistant-text hash to connection) to preserve prompt caching, normalized into opencode ses_ format with a generated fallback. When the downstream client is already opencode, its headers are forwarded as-is.
2026-08-13 12:13:51 +07:00
stoXmod
b566b20ade fix(antigravity): strip competitive system prompts to prevent 429 quota errors
Zed IDE injects a Claude-agent system prompt that Antigravity flags as
competitive, blocking the request with a 429 Quota Exhausted response.
Scan systemInstruction.parts and remove the prompt before dispatch.
2026-08-13 12:11:30 +07:00
Clayton Tavares
6d30ce6de5 fix: Fusion strip stream_options + reasoning model test probe
- combos: strip stream_options from Fusion panel fan-out to avoid DeepSeek 400 (#3024)
- dashboard: raise model-test probe budget to 1024 + soft-pass reasoning-only responses (#3010)
2026-08-13 11:56:43 +07:00
rm1dev
5b417f9bf2 fix(kiro): intercept chat via x-amz-target and prepend initial-response frame
Kiro IDE 1.0.228+ moved GenerateAssistantResponse from path
/generateAssistantResponse to POST / + x-amz-target header, so chat turns
bypassed MITM. The SmithyMessageDecoderStream also now requires an
initial-response frame at stream start, and agent/vibe mode sends
modelId "auto" which had no mappable slot.

- Add isChatRequest() header-based match for kiro in mitm/config.js
- Add buildInitialResponseFrame/withInitialFrame to emit the mandatory
  initial-response once per stream (kiro.js)
- Add "auto" model slot and update mitmDomain to runtime.us-east-1.kiro.dev
2026-08-13 11:56:31 +07:00
Cokky Turnip
b57c041345 fix(providers): add llm7 to provider test support 2026-08-13 11:56:06 +07:00
zmf
8a527fec91 fix(security): SSRF guard on search baseUrl, default-password remote login, and request-details redaction
- resolveBaseUrl() rejects client-supplied non-public baseUrls via assertPublicUrl (SSRF guard on /v1/search)
- fresh-install remote login with default password returns 403 without issuing a JWT
- /api/usage/request-details redacts request/providerRequest/providerResponse/response payloads
- declare chalk and prop-types in package.json (used but previously undeclared)
2026-08-13 11:50:25 +07:00
Nguyen Thanh Dat
70ba0024b0 fix(translator): preserve prompt_cache_key when converting chat to responses 2026-08-13 11:46:49 +07:00
brimob-sowax
80afb59907 fix(qoder): detect billing blocks at stream start, return 403 for failover
Peek the first SSE frame in wrapQoderSSE; if statusCodeValue != 200 and the
body carries a billing signature (code 112/10605 or pricingUrl), return a
synthetic 403 so chatCore marks the connection unavailable and triggers
combo/account fallback instead of leaking the error text into chat.

wrapQoderSSE becomes async; consumed peek bytes are re-processed in the
stream start() seed loop so nothing is dropped.
2026-08-13 11:43:11 +07:00