* fix(oauth): stop treating a foreign window.opener as a bind flow
The /oauth/:provider callback decided between an account bind and a plain
login with `window.opener ? 'bind' : 'login'`. Any tab opened from an
external link (target="_blank", Slack, mail clients, another site) carries
a live opener, and that opener survives the cross-origin round trip to the
identity provider. Such a login callback was therefore misread as a bind:
it posted a handshake to a window that speaks no such protocol, showed the
"binding your account" screen, and hung until the 30s deadline fired with
"OAuth binding timed out" — while the backend was never called at all.
Reproduced against a real Keycloak round trip: a tab opened via window.open
still reports window.opener !== null on the callback, so mode resolved to
'bind' for an ordinary OIDC login.
A bind now requires positive proof: the popup we open for it is same-origin
(about:blank) before being sent to the provider, so we stamp its own
sessionStorage. The stamp rides through the provider round trip and is
scoped to that popup alone, so a login tab can never carry it. Ambiguity
resolves to 'login', which is the recoverable direction.
Affects every provider sharing this callback (OIDC, GitHub, Discord,
LinuxDO, custom).
* fix(oauth): harden bind popup detection
* docs: add design spec for OIDC custom display name
Mirrors the existing Custom OAuth Provider name pattern so admins can
show a meaningful label instead of the hardcoded "OIDC" on the login
page and in related copy.
* feat(oidc): add configurable display name with OIDC fallback
* feat(oidc): use configured display name in provider name and status API
* feat(oidc): add display name field to default-theme OIDC settings
Claude-Session: https://claude.ai/code/session_01FDkWJqigJi9yE3HG5pjZP5
* feat(oidc): show configured display name on default-theme login button
* feat(oidc): add display name field to classic-theme OIDC settings
* feat(oidc): show configured display name on classic-theme login button
* fix(oidc): trim whitespace before applying display name fallback
* chore: remove internal design doc from PR
Design/planning docs are working artifacts for this session and
shouldn't be submitted to the upstream project.
* fix(oidc): lead with example in classic-theme display name placeholder
Reorders the combined placeholder to show the example first, then
the fallback note, matching the Custom OAuth Provider Name field's
placeholder convention (example-only) that this feature mirrors.
* fix(i18n): improve Russian grammar in OIDC display-name placeholder translation
Leads each clause with its condition/subject and adds the missing
verb, per PR review feedback.
* test(web): remove redundant OIDC harness tests
* test(relayconvert): add golden snapshot matrix and relaykit boundary guard
Phase 0 of the relaykit extraction plan: pin byte-level output of every
registered (from,to) request/response/stream conversion route, and
forbid kit-bound packages from growing host-only imports.
* wip(relayconvert): drop gin.Context from converter signatures; add convmeta draft
Phase 1 in progress: relayconvert now takes context.Context; host media
resolver adapts gin.Context back at the service boundary.
* refactor(relayconvert): decouple converters from RelayInfo, gin, and settings
Phase 1 of the relaykit extraction plan:
- converters now depend on convmeta.Meta (implemented by RelayInfo) instead
of *relaycommon.RelayInfo; ClaudeConvertInfo and the format guesser move
to convmeta with aliases left behind
- host settings reach converters via a convmeta.Options snapshot built in
RelayInfo.ConvOptions; no more model_setting/reasoning global reads inside
the conversion layer
- effort-suffix helpers move to service/relayconvert/reasoning (old package
forwards); chat-to-responses upgrade policy moves to service (host routing
logic, not conversion)
- golden conversion matrix unchanged
* test(relayconvert): tighten boundary — kit packages now free of gin/setting imports
* refactor(dto): drop gin and logger dependencies
Phase 2 (part 1): dto.Request.IsStream now takes *http.Request instead of
*gin.Context (Gemini's impl reads query/path off the std request); dto's
three logger calls become common.SysError. Boundary test allowlist is now
empty — kit-bound packages import no gin/setting/logger/model.
* refactor(kit): extract dependency-free kitutil; dto/types/relayconvert stop importing common
Phase 2 of the relaykit extraction plan:
- new service/relayconvert/kitutil holds the pure helpers the kit needs
(JSON wrappers, pointer/string/uuid/timestamp utils, MaskSensitiveInfo,
pluggable LogInfo/LogError hooks, Debug flag)
- dto, types, and all relayconvert packages now use kitutil; their only
remaining internal deps are dto/types/constant
- common keeps every original symbol (MaskSensitiveInfo delegates to
kitutil) so host code is untouched; main.go routes kit logging into
common.SysLog/SysError and mirrors DebugEnabled
- golden conversion matrix unchanged
* refactor(kit): move EndpointType/FinishReason to types; OpenRouter dialect via Options
Kit packages (dto/types/relayconvert/reasonmap) no longer import constant:
- EndpointType and finish-reason values live in types; constant re-exports
- the OpenRouter special-case in claude->openai request conversion reads
Options.OpenRouterDialect, set by the host from the channel type;
InitChannelMeta invalidates the cached snapshot on channel switch
* refactor: extract relaykit submodule (dto/types/relayconvert/reasonmap)
Phase 3 of the relaykit extraction plan:
- new go module github.com/QuantumNous/new-api/relaykit containing dto
(minus task family), types, relayconvert (with convmeta/kitutil/reasoning),
and reasonmap; host consumes it via require + replace, go.work for dev
- task-family dto (task/suno/midjourney/video) stays in the host dto
package; dual-consumer host files alias it as taskdto
- relaykit builds and tests standalone (GOWORK=off): no host imports,
no gin, no DB, no settings
- golden conversion matrix unchanged
* build(docker): copy relaykit/go.mod before go mod download
The local-replace submodule's go.mod must exist inside the build context
for the main module graph to resolve.
* fix: address relaykit extraction regressions
* fix: address relaykit review regressions
* docs: document Meta nil receiver contract
* fix(relaykit): fail OpenAI→Claude conversion without max_tokens; reject negative default_max_tokens
The Claude Messages API requires max_tokens (omitting it is a 400
"Field required"), but with a nil Options.Claude.DefaultMaxTokens hook
the converters silently emitted a request the upstream is guaranteed to
reject. Both OpenAI Chat and Responses → Claude conversions now return
sharedclaude.ErrMissingMaxTokens when no path (client value, default
hook, thinking-adapter floor) supplied one. Unreachable in the host,
which always configures the hook.
Host side, claude.default_max_tokens now rejects negative values at the
option API before persisting — they would wrap into huge unsigned values
during conversion. Zero stays allowed: the current API treats
max_tokens: 0 as cache pre-warming.
* fix: make Gemini safety settings read path race-free
* feat: support tencent tokenhub api key via openai-compatible protocol
* fix: use tokenhub base url for tencent api key channels
* test: cover tencent key-format dispatch and add TokenHub key prompt locales
The line-number layer spans the full editor, so giving it an opaque
background and raised z-index covered the highlighted content. Reverts
the gutter background added in 88868002f; the horizontal-scroll
gutter overlap it targeted returns as a known cosmetic issue.
* perf(json-editor): improve JSON editing experience
- integrate Yace for syntax highlighting, history, indentation, auto-closing, and smart line breaks.
- add copy support, cursor location feedback, and synchronized content and line-number scrolling.
- extract JSON editor utilities and cover key interactions with unit tests.
* perf(system-settings): improve JSON configuration editing
- replace raw JSON textareas with the shared editor for highlighting, validation, copy, and formatting.
- preserve field-specific examples and make placeholders visible through the transparent editor layer.
- remove duplicate formatting controls while keeping existing form validation and save behavior.
* perf(json-editor): standardize JSON inputs across admin settings
- replace pure JSON textareas with the shared editor across system settings and channel workflows.
- preserve form focus, validation, placeholders, and visual or JSON editing modes.
- add happy-dom component coverage for form bindings, controlled updates, and formatting.
* fix(json-code-editor): address accessibility review findings
- Drop the unconditional aria-label that overrode every field's
label-derived accessible name; add an optional ariaLabel prop and
set it at call sites without an associated label
- Associate standalone Labels via htmlFor/id in channel-affinity views
- Hide the highlight mirror and line-number layers from the
accessibility tree (aria-hidden)
- Give the line-number gutter an opaque background so horizontally
scrolled code no longer slides under it
- Degrade to no scroll sync instead of destroying the editor when the
line-number layer is not found
* fix(channel): debounce inline priority updates
* fix(channel): commit spinner edits only on Enter or focus leave
Blurring the inline edit input to the +/- buttons flushed the pending
priority update immediately, bypassing the container focus-containment
check and defeating the debounce. Commit now happens via the container
blur handler or explicitly on Enter. Add unit tests for the priority
update scheduler.
* fix(channel): preserve row identity when priority updates reorder channels
* fix: prevent model create from wiping existing pricing for same name
* fix(models): keep create from wiping pricing for any existing name
Prefilling from the drawer's open-time model name only covered the
missing-models entry point. Submit deletes the pricing entries for the
name in the form, which is editable and starts empty from the toolbar
"Create model" button, so both a hand-typed existing name and a renamed
prefill still dropped the configured pricing.
Track the name whose pricing was actually read into the form and scope
the delete-then-readd to it, or to a name the user explicitly priced.
Editing still clears pricing by emptying the fields, a prefilled create
does too, and a name the form never loaded is left alone -- which also
stops an edit that renames onto an existing name from destroying that
name's pricing.
Read the prefill through one shared readPricingConfig instead of
duplicating the seven-map parse in both branches, and open the advanced
section for ratios that are configured as 0 rather than only truthy ones.
When an upstream error response parses as valid JSON but yields no usable
error message (e.g. an aggregator gateway returning {"error":{"message":""}}),
RelayErrorHandler previously produced a bare "bad response status code N"
error with no trace of the original body, making the failure undiagnosable.
Log the body preview in that case, mirroring the existing behavior for
unparseable bodies.
When the dashboard token refresh endpoint returned 429 (shared
critical rate limit) the frontend classified it as out_of_sync,
cleared local auth state, and redirected to /sign-in. The rate limit
itself is working as intended; the bug is that a temporary rejection
was treated as a terminal auth failure.
- Treat 429 refresh responses as transient errors on the frontend,
keeping the session retryable instead of clearing it. Only explicit
401 or confirmed session mismatch/race exhaustion clears auth state.
- Return Retry-After on all rate-limited responses (remaining TTL on
Redis, window duration on the in-memory limiter) so clients can
back off.
- Log the underlying error with request context when auth session
errors map to 500 AUTH_INTERNAL_ERROR, and replace fmt.Println with
request-scoped logging in the Redis rate limiter error paths.
Fixes#6361
* fix(playground): resolve auto group model listing
- merge and deduplicate available models in configured auto group order.
- reuse special usable group rules and add model filtering regression coverage.
* refactor: extract GetGroupsEnabledModels to dedupe group model expansion