879 Commits

Author SHA1 Message Date
musi aec22a00cc Merge pull request #1682 from songkuan-zheng/feat/ui-provider-advanced-json
feat(ui): edit extraBody and extraHeaders from Advanced settings
2026-08-27 22:41:36 +08:00
musi 46e3232913 Merge pull request #1699 from krajcik/fix/gateway-preload-undici-major-mismatch
fix(gateway): pair dispatchers with bundled undici fetch on undici major mismatch (Node >= 25)
2026-08-27 22:35:35 +08:00
musi 44e3a52990 Merge pull request #1715 from diogomcd/fix/responses-unsupported-metadata
fix: skip session affinity body fields for Codex Responses upstreams
2026-08-27 22:25:18 +08:00
musi e7ff249367 Merge pull request #1711 from diogomcd/fix/invalidate-claude-gateway-model-cache
fix: invalidate Claude gateway model discovery cache when the profile allowlist changes
2026-08-27 22:19:55 +08:00
musi 1798335254 Merge pull request #1720 from diogomcd/feat/claude-app-sync-entry-mode
feat: respect profile Entry mode when syncing the Claude App config
2026-08-27 22:03:08 +08:00
krajcik 98b30c2510 refactor(gateway): defer dispatcher pairing to ai-gateway 1.0.18 2026-08-26 15:35:41 +03:00
Diogo Macedo 7c1c0d49b4 feat: respeita Entry mode dos perfis no sync da config do app Claude 2026-08-25 16:17:27 -03:00
musi 99f24806c6 update sponsor 2026-08-25 22:03:48 +08:00
songkuan-zheng 7f2500980d fix(ui): document the raw ai-gateway extraBody shape instead of byModel
Review follow-up: byModel is ai-gateway's normalized internal form
(parseModelScopedBody treats only `default` and actual model names as
scope keys); the raw config saved to disk is `default` plus top-level
model-name keys. The help text, i18n rows and the round-trip test now
use that raw shape.
2026-08-25 06:08:44 +00:00
songkuan-zheng e4ad5db099 feat(ui): edit extraBody and extraHeaders from Advanced settings
The provider dialog had no field for either, so both were config-file
only: invisible in the UI and only editable by hand-writing the config
or writing to the config database directly.

Adds two JSON boxes to the Advanced settings section, next to the usage
connector body that already uses this pattern. They open pre-filled with
whatever the provider carries, and a malformed or non-object value blocks
the save with an inline error instead of writing something the gateway
would ignore.

extraBody and extraHeaders leave providerManualFieldsForSave now that the
draft round-trips them; billing, provider and transformer stay there.
2026-08-25 06:08:22 +00:00
songkuan-zheng a5a5481307 test(e2e): pin the provider-edit spec to the en-US locale
Review follow-up: the spec hard-codes English UI strings ("Providers"
nav, the "Edit <name>" provider aria-label), but the web UI defaults to
the system language, so the suite failed on zh-locale hosts. The
Playwright context locale is now pinned per suite, which keeps the
selectors deterministic without dual-language lookups everywhere.
2026-08-25 06:08:13 +00:00
songkuan-zheng bb6c256b06 test(e2e): cover provider fields the setup dialog cannot edit
Drives the real management UI: seeds a provider carrying extraBody,
extraHeaders and a transformer, opens the edit dialog, saves, and asserts
the three fields are still there afterwards.

The spec fails on the unpatched build and passes with the fix, so it
guards the regression rather than the helper alone.

The CLI web server bootstrap moves out of cli-web.spec.ts into
cli-web-runtime.ts so both specs can start their own instance.
2026-08-25 06:08:13 +00:00
songkuan-zheng 1e696434c8 fix(ui): keep hand-written provider fields when saving from the dialog
Saving a provider from the setup dialog replaced the stored entry with an
object rebuilt from the dialog draft, so every field the form does not
expose was dropped: extraBody, extraHeaders, transformer, billing and the
legacy provider protocol alias.

These only ever come from a hand-written config, and the loss is silent —
the provider keeps working, just without the upstream parameters it was
configured with. Re-typing a base URL on a provider carrying
`extraBody: { default: { reasoning_effort: "high" } }` is enough to turn
reasoning off with no error and no visible change in the UI.

Carry those fields over from the existing provider on edit. The legacy
apiKey/apikey/baseUrl/baseurl aliases are deliberately not carried over:
the form writes the canonical api_key/api_base_url, and a stale alias
would shadow the value the user just typed.
2026-08-25 06:08:13 +00:00
Diogo Macedo 23592a893b fix: contain Claude model discovery cache invalidation failures in the claude-code profile status 2026-08-24 12:06:31 -03:00
Diogo Macedo 6040d66f37 fix: hash generated Claude discovery and bootstrap payloads in the model cache fingerprint 2026-08-24 11:51:46 -03:00
Diogo Macedo c15b668910 fix: skip session affinity body fields for codex responses upstreams 2026-08-24 10:41:34 -03:00
musistudio 829298cf8b Update model catalog for 3.0.22 v3.0.22 2026-08-24 20:19:33 +08:00
musistudio 19e5d4909e Release 3.0.22 2026-08-24 20:16:17 +08:00
musistudio f2360ac046 Merge branch 'main' into dev/3.1 2026-08-24 20:02:57 +08:00
musi 17c8744de9 Merge pull request #1695 from songkuan-zheng/fix/fusion-vision-validate-image-inputs
fix(fusion-vision): validate and repair image inputs before forwarding
2026-08-24 20:02:20 +08:00
musi 3e979650fd Merge pull request #1713 from pacocartones/fix/media-download-release-response-body
fix(media): release artifact response body on early download failures
2026-08-24 19:44:06 +08:00
musistudio 70e7fd1a80 Secure gateway preload file permissions 2026-08-24 19:29:15 +08:00
musistudio 9e7d4f782c Implement router enhancements 2026-08-24 19:02:41 +08:00
pacocartones 6c98ea2adf fix(media): release artifact response body on early download failures
GatewayMediaExecutor.download() left the fetch Response body un-read and un-cancelled on several early-exit paths: a terminal non-ok HTTP status, an oversized declared content-length (declaredLength > maxApiArtifactBytes), and a mid-stream writeSync failure in the download loop. In each case the underlying undici connection/socket is leaked, so repeated failed downloads accumulate open connections and exhaust the pool.

Cancel the response body before throwing on those paths, and cancel the reader in the write-error handler, matching the cancellation already done on the retry branch and the in-loop size-limit branch.
2026-08-24 04:28:06 +00:00
Diogo Macedo b931866671 fix: invalidate gateway model discovery cache when profile allowlist changes 2026-08-23 15:26:17 -03:00
musi 1347c868b4 Merge pull request #1691 from dylanpulver/fix/responses-session-affinity
Forward session id as prompt_cache_key on openai_responses
2026-08-22 18:34:25 +08:00
musi 5475a12a23 Merge pull request #1700 from diogomcd/fix/provider-account-meters-flattening
fix: preserve provider account meters during local agent import
2026-08-22 18:16:27 +08:00
musi e2eb944f5a Merge pull request #1705 from kskadart/fix/claude-code-auth-hook-regression
fix(core): cover Claude Code path in local agent auth provider hook
2026-08-22 17:30:17 +08:00
musistudio ccd00632ac Update project files 2026-08-22 16:12:46 +08:00
musistudio d515556574 Fix profile-specific model routing and gateway shutdown 2026-08-22 10:18:18 +08:00
kskada acf75d1280 Add Claude Code regression test for local agent auth provider hook 2026-08-21 16:29:58 +03:00
Diogo Macedo 186541d0fd fix: preserve provider account meters during import 2026-08-20 16:43:13 -03:00
musistudio 46daaad92e Resolve Claude Code routes with one-million-context model suffixes 2026-08-20 16:45:54 +08:00
songkuan-zheng 3dab369ce3 test(fusion-vision): regression coverage for data-URL base64, local SVG, label restriction 2026-08-20 04:13:13 +00:00
songkuan-zheng fc16be5d41 fix(fusion-vision): restore data-URL imageBase64, sniff local files, limit media-type labels
Review follow-up (three defects):

1. imageBase64 accepted a full data:...;base64,... URL per its schema
   documentation, but the new validation passed the whole string to the
   base64 check and rejected it. Data URLs now take the same parsed path
   as imageUrl via a shared dataUrlResult() helper.

2. Local files (imagePath/images[].path) were forwarded after wrapping
   buffer bytes without inspecting them, so a .svg on disk went upstream
   as a fake raster data URL and drew the exact strict-provider 400 this
   work avoids. File bytes now go through the same content checks as every
   other input.

3. The data URL media-type label was taken verbatim from the URL header or
   the mimeType argument. Labels are now restricted to the supported raster
   types (jpeg/png/gif/webp): an explicit supported label wins, otherwise
   the format sniffed from the bytes, otherwise image/png.
2026-08-20 04:12:58 +00:00
musi 6e1a9617ff Merge pull request #1672 from pacocartones/fix/sse-utf8-chunk-boundary
fix(gateway): SSE transforms corrupt multi-byte characters split across chunks
2026-08-20 11:23:31 +08:00
musistudio 053bcad485 Add Xingxin to provider listings 2026-08-20 10:11:51 +08:00
krajcik 59420662bd fix(gateway): pair dispatchers with bundled undici fetch on major mismatch
Node >= 25 ships undici 8 as its built-in fetch while CCR bundles undici 7.
A dispatcher created by one undici major fails inside the other's fetch with
UND_ERR_INVALID_ARG ("invalid onError method"), so every gateway upstream
request fails with 502 "All target providers failed" (stage upstream_connect)
regardless of provider or proxy settings.

The generated gateway-proxy-preload.cjs already intercepts global fetch, but it
passed dispatcher-carrying calls straight to the runtime's built-in fetch and
injected bundled dispatchers into it. Route every call that carries or receives
a bundled dispatcher through the bundled undici's own fetch when the runtime
and bundled undici majors disagree; keep the previous behavior when they match.
2026-08-19 23:37:11 +03:00
musi a1862982ae Merge pull request #1696 from Burlesque1/fix/large-json-body-and-extraheaders
fix(ui): truncate over-large JSON bodies in preview to avoid formatter worker crash
2026-08-19 17:58:27 +08:00
musistudio 3ba195f893 Improve Claude Code router configuration and provider support 2026-08-19 17:19:07 +08:00
Burlesque1 cfb6aae248 fix(ui): truncate over-large JSON bodies in preview to avoid formatter worker crash
Large (>=256KB) JSON request bodies were fully JSON.parse'd and pretty-printed
in the body formatter Web Worker, which then crashed (memory/CPU) on huge object
graphs - surfacing "Body formatter worker failed." and sticking at
"Loading body...". Preview truncation only applied to non-JSON bodies.

Now, in preview mode, an over-length text body is truncated to a plain-text
preview (createLogBodyPreviewText) instead of being parsed/pretty-printed, so the
worker never chokes. Full formatting still runs in "full" mode.

The judge is the real body.text length, not sizeBytes (which may be inflated
metadata and should still render its JSON tree when lightweight).

Fixes #1694
2026-08-19 08:55:14 +00:00
songkuan-zheng 1691aa957b fix(fusion-vision): validate and repair image inputs before forwarding
imageInputToUrl currently forwards whatever imageUrl/imageBase64 carries as
long as it is not an HTTP(S)/data URL, wrapping it in a blind
data:image/png;base64,... envelope. Strict upstreams answer malformed
payloads with a 400 that surfaces to the caller as a raw provider error.

Failure shapes seen in production:
- a local file path passed as imageUrl, or a bare [media_ref:...] id
- base64 truncated to length mod 4 == 1 (cut mid-image; padding cannot
  restore it, the payload is dropped)
- an XML/SVG payload (typical vision upstreams accept only jpeg/png/gif/
  webp, so relabeling it buys nothing)
- base64 truncated to length mod 4 == 2/3 (losslessly repairable by
  adding = padding)

Anything that cannot be made into a well-formed data URL is now dropped
with a precise skip reason, and a call left with no usable image fails
with a message listing every reason instead of silently proceeding.
2026-08-19 08:41:19 +00:00
musistudio b073ebef31 Add Fusion vision instructions and image capabilities 2026-08-19 16:06:21 +08:00
Dylan Pulver 51c0fe7d12 Forward session id as prompt_cache_key on openai_responses
Requests converted to the OpenAI Responses protocol left the outbound
body without any session-stable field: prompt_cache_key was never set
and the inbound Anthropic metadata.user_id was dropped. Multi-channel
Responses upstreams that pin sessions on body fields hashed each turn
onto a different channel, so channel-bound encrypted_content
continuations failed with 400 invalid_encrypted_content and prefix
cache hits were lost.

The gateway boundary plugin now fills prompt_cache_key on outbound
openai_responses JSON bodies from the first non-empty of
x-claude-code-session-id, x-claude-session-id, or the inbound
metadata.user_id, and carries metadata.user_id onto the outbound body
when no metadata was set. A caller-supplied non-empty prompt_cache_key
always wins, and other protocols pass through untouched.

Fixes #1688
2026-08-18 12:15:21 +03:00
musistudio 586dd0242f Expose browser account fetch helper to runtime plugins 2026-08-18 15:39:03 +08:00
musistudio 4344a2d6a7 Refactor routing configuration and improve provider support 2026-08-18 11:45:39 +08:00
musi fcf3d85da1 Merge pull request #1681 from songkuan-zheng/fix/usage-cache-convention-by-source
Normalize usage token conventions per source
2026-08-17 19:52:24 +08:00
songkuan-zheng 17fd745898 Normalize usage token conventions per source
normalizeUsageInputTokens decides whether input_tokens already includes the
cached prefix, and it asked the upstream provider protocol first. Both usage
call sites merge the billing headers and the response body into one snapshot
and then normalize that merge once — but on a translated response the two
sources use different conventions:

  - x-gateway-billing-* headers restate the upstream provider's own counters
    verbatim, so for an OpenAI-compatible upstream they are cache-inclusive.
  - The response body is whatever the gateway emitted. An Anthropic body is
    cache-exclusive regardless of what it was translated from.

So an Anthropic response served from an OpenAI-compatible upstream had the
body's already-excluded prefix subtracted a second time. Non-streaming
responses were unaffected by luck: the headers win the merge and are
inclusive, making the subtraction correct. Streaming responses carry no
billing headers at all, leaving the body alone to be over-subtracted and then
clamped by Math.max(0, ...) — on a long cached conversation input_tokens
reports 0 on nearly every turn, and the derived cache ratio is pinned at
100%.

Tag each source with a UsageConventionSource and normalize the two separately
before merging them. Reordering the precedence instead is not sufficient: it
moves the defect onto the non-streaming path, which depends on the headers
being reduced.

Raw-trace updates carry no provider protocol, so their billing headers keep
falling back to the request path; only the body side changes there.
2026-08-17 05:21:30 +00:00
musistudio 22b68fd907 Qualify target model selectors with routed provider 2026-08-15 23:47:32 +08:00
musistudio 83e01c3077 Sanitize target model header while preserving Unicode body 2026-08-15 23:16:15 +08:00