Commit Graph
207 Commits
Author SHA1 Message Date
Seefs 85feb7a345 feat(relay): expose user and group context to parameter overrides (#6534) 2026-08-10 12:49:06 +08:00
Seefs eab18a8357 fix: record reasoning effort consistently in usage logs (#6641) 2026-08-10 12:48:14 +08:00
CaIon ea4f021012 refactor(relay): move replay metadata onto request bodies 2026-08-06 17:33:10 +08:00
LucasandClaude Fable 5 d6b5ce99de fix(relay): set Request.GetBody so the HTTP/2 transport can transparently retry after an upstream stream reset (#6249)
* fix(relay): set Request.GetBody so the HTTP/2 transport can transparently retry after an upstream stream reset

The outbound request body is a type-erased io.Reader over BodyStorage, so
net/http cannot derive Request.GetBody (it only does so for *bytes.Reader,
*bytes.Buffer and *strings.Reader). With GetBody nil, the HTTP/2 transport
cannot transparently retry a request once the body has been written and the
upstream resets the stream with a retryable error (REFUSED_STREAM, or a
connection-level GOAWAY); the relay request then fails with:

    http2: Transport: cannot retry err [...] after Request.Body was written;
    define Request.GetBody to avoid this error

This affects every relay path that goes through DoApiRequest (chat, claude,
gemini, responses, embedding, image, rerank).

BodyStorage (memory and disk) already implements io.Seeker, so replay support
only needed wiring:

- NewOutboundJSONBody additionally returns a getBody that rewinds the storage
  and hands out a fresh non-closing reader. The transport only calls GetBody
  after the previous attempt's body has been abandoned, so the rewind cannot
  race an in-flight read.
- RelayInfo carries it in the new UpstreamRequestGetBody field, set alongside
  UpstreamRequestBodySize by the handlers that build storage-backed bodies.
- applyUpstreamGetBody (symmetric with applyUpstreamContentLength) wires it
  into DoApiRequest/DoFormRequest/DoTaskApiRequest, only when req.GetBody is
  still nil.

Also remove the hand-rolled GetBody override in DoTaskApiRequest: it returned
the same already-consumed reader, so any transport-level replay would have
silently sent an empty body, and it clobbered the correct snapshot-based
GetBody that net/http derives from the *bytes.Reader bodies the task adaptors
pass in. For non-replayable bodies GetBody now stays nil, so a retry fails
loudly instead of corrupting the request.

Covered by unit tests plus an end-to-end raw-frame HTTP/2 test that resets
the first stream with REFUSED_STREAM after the body is written and asserts
the transport transparently retries with the complete body.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(relay): hand out independent readers from GetBody (address review)

Per the http.Request.GetBody contract ("returns a new copy of Body"),
each call must yield a reader with its own cursor. The previous
implementation rewound and reused the shared BodyStorage, so two
consecutive GetBody readers would interfere with each other, and a
replay could disturb the primary body's offset under extreme transport
timing (e.g. attempt N's body write not yet fully abandoned when the
transport builds attempt N+1).

Instead of snapshotting the payload (an extra copy), add
BodyStorage.NewReader, which returns an independent zero-copy reader:

- memory mode: a fresh bytes.Reader over the same immutable backing
  array;
- disk mode: a separate file descriptor over the cache file, so the
  transport closing a replayed body only closes that descriptor.

NewOutboundJSONBody's getBody now simply hands out storage.NewReader,
and once the handler releases the storage, GetBody fails with
ErrStorageClosed instead of replaying stale data.

Tests: interleaved reads across two replay readers and the primary
body each observe exactly their own byte stream, for both the memory
and the disk-backed storage; the existing GetBody and HTTP/2 retry
suites still pass (h2 e2e tests flake-free with -count=20).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(relay): bind replayable metadata on pass-through requests

* fix(relay): reset upstream body metadata between channels

* test(relay): cover replay across retries and channel attempts

* fix(relay): stop following upstream redirects

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-06 15:56:38 +08:00
吴天一 df43f80153 fix(billing): settle tiered retries with final group (#6518) 2026-07-31 19:16:46 +08:00
CaIon b8bb3f40ac refactor: update import paths to use new types package 2026-07-27 16:45:02 +08:00
Calcium-Ion 86ac0f7745 refactor: extract protocol conversion layer into standalone relaykit module (#6369)
* 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
2026-07-27 15:56:21 +08:00
CaIon 398cdafecf feat: add New API channel support 2026-07-27 15:20:19 +08:00
CaIon 2d23cdf291 feat: configurable tool pricing, Sub2API channel, and alpha search billing
Add admin-configurable tool-call prices with cross-provider surcharge
settlement, Sub2API channel support, /v1/alpha/search relay, and usage-log
surcharge UI.
2026-07-26 20:05:15 +08:00
feitianbubu 08f88d25e5 feat: support Tencent TokenHub API key via OpenAI-compatible protocol (#6232)
* 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
2026-07-25 22:05:09 +08:00
Calcium-Ion c36418c863 feat: enhance text protocol conversion and advanced custom routing (#5825)
* refactor: consolidate relay protocol converters

* refactor relayconvert text converters

* feat: refine relay converters and advanced custom routing

* refactor: enhance logging and add thought signature handling for Gemini requests

* refactor: enhance channel cache and pricing endpoint handling for advanced custom models

* feat: preserve billing usage semantics

* feat: add protocol-aware billing usage

* Delete useless files

* chore: update action versions in workflow files

* chore: update Docker action versions in workflow files

* fix: harden billing usage settlement and hot-path route matching

- estimate Gemini completion tokens locally when billable usageMetadata is
  prompt-only but output content was received (e.g. client aborts the stream
  before the final chunk), and rebuild the attached billing_usage as estimated
  so settlement does not bill zero output tokens
- guard NewClaudeMessagesBillingUsage against all-zero ClaudeUsage, matching
  the OpenAI/Gemini constructors, so a zero billing_usage cannot override a
  non-zero top-level usage during settlement
- cache compiled advanced-custom route model regexes; they run on the request
  hot path and were recompiled per request
- move the effectiveBillingUsage remap to PostTextConsumeQuota only, and
  document that calculateTextQuotaSummary expects remapped usage
- document the updatePricingLock -> channelSyncLock lock ordering that
  InitChannelCache/CacheUpdateChannel rely on, and the aux-struct pitfall in
  GeminiChatResponse.UnmarshalJSON
2026-07-11 20:44:12 +08:00
CaIon bae799ccb1 fix(billing): surface quota saturation events for admin auditing
Thread int32 saturation clamps from tiered settlement and video task
recompute into the consume/task logs under admin_info, so oversized or
malformed billing inputs stay auditable. Clamp negative audio duration
before token conversion and gate the saturation UI markers on admin.
2026-07-07 12:20:07 +08:00
CaIon d0bd8aac74 fix(billing): validate quantity parameters and harden quota calculations
Bound user-supplied count/duration parameters at request validation,
route ratio multipliers through guarded setters, and use saturating
int conversions in all quota math paths.
2026-07-07 00:21:06 +08:00
Scott 52858ad1e6 feat: support Wan2.7 i2v media mapping (#4984)
* feat: support Wan2.7 i2v media mapping

* fix: normalize wan2.7 i2v image inputs
2026-07-01 21:21:41 +08:00
Calcium-Ion 6dc4030fdf feat: support ClickHouse log database (#5663)
* feat: support ClickHouse log database

* feat(log): optimize log deletion process for ClickHouse
2026-06-22 18:41:26 +08:00
Seefs 3f2c0aeda7 feat: advanced custom channel (#5590) 2026-06-18 18:00:27 +08:00
CaIon fddf54ccc5 perf: reduce heap residency for large base64 relay requests
Three layered optimizations targeting Gemini-style 5MB base64 payloads where
RSS could balloon to tens of GB under concurrent load:

1. Byte-based param override (relay/common/override.go)
   - Switch legacy/operations hot paths from common.Marshal round-trips and
     map[string]any conversions to gjson/sjson on []byte directly.
   - Avoids cloning 5MB strings during each Set/Delete operation.

2. strings.Builder for Gemini response markdown (relay/channel/gemini/relay-gemini.go)
   - Replace string concatenation + strings.Join when assembling
     "![image](data:...;base64,DATA)" content for inline image responses.
   - Pre-allocates capacity from inline_data byte sizes.

3. Outbound BodyStorage + streaming Decoder (this commit's core)
   - New relay/common/outbound_body.go helper wraps marshaled upstream bodies
     in common.BodyStorage, allowing disk-cache mode to offload jsonData to
     a temp file while waiting for upstream TTFB. The original []byte can
     then be GC'd, removing ~5MB/req of heap residency during the longest
     window of a request.
   - All 7 relay handlers (gemini/claude/responses/embedding/image/compatible/
     rerank) plus chat_completions_via_responses adopt the helper with
     defer closer.Close() and explicit jsonData = nil.
   - relay/common/relay_info.go: new UpstreamRequestBodySize so
     relay/channel/api_request.go can populate req.ContentLength (lost when
     body becomes a type-erased io.Reader).
   - common/gin.go UnmarshalBodyReusable: when storage is disk-backed and
     content-type is JSON, decode via DecodeJson(storage) instead of
     storage.Bytes()+Unmarshal, removing one transient 5MB copy per request.
     memory mode and form/multipart paths unchanged.
2026-05-22 19:08:38 +08:00
Seefs ae6a03364d perf: optimize request metadata extraction and disabled field filtering (#5009)
* perf: optimize request metadata extraction and disabled field filtering

* perf: optimize stream usage estimation path
2026-05-22 10:32:11 +08:00
Seefs 0d4b25795a fix: expose param override audits for sensitive message fields (#4974) 2026-05-19 18:28:03 +08:00
CaIon 6bde1a9c8d Merge origin/main into nightly
Resolve conflicts:
- .gitignore: keep nightly additions (.test, skills-lock.json)
- relay/helper/price.go: keep both billingexpr and model imports
- en.json / zh-CN.json: keep nightly's superset of i18n entries
- service/billing_session.go: add missing 3rd arg to DecreaseUserQuota
- en.json / zh-CN.json: deduplicate 129+320 duplicate i18n keys
2026-04-23 21:37:03 +08:00
Seefs f7adf02eb4 feat(claude): add cache_control and speed passthrough controls (#4247) 2026-04-15 20:55:01 +08:00
skynono b4df9955f4 fix: isStream status in error logs instead of hardcoded false (#4195) 2026-04-12 17:41:26 +08:00
CaIon 4d2993e4cc Merge remote-tracking branch 'origin/main' into nightly
# Conflicts:
#	web/src/helpers/render.jsx
#	web/src/hooks/usage-logs/useUsageLogsData.jsx
#	web/src/i18n/locales/en.json
2026-04-09 17:12:21 +08:00
feitianbubu b713e277cd feat: metadata correct parse 2026-04-03 15:28:08 +08:00
feitianbubu 08a5243bbc feat: TaskSubmitReq support Duration 2026-04-03 15:00:23 +08:00
CaIon 35d0704640 Merge branch 'origin/main' into nightly
Resolve 4 conflicts:
- relay/compatible_handler.go: accept main's refactor (postConsumeQuota -> service.PostTextConsumeQuota)
- service/quota.go: accept main's PostClaudeConsumeQuota deletion, keep nightly's tiered billing in PostWssConsumeQuota and PostAudioConsumeQuota
- web/src/i18n/locales/{en,zh-CN}.json: merge both sets of translation keys

Post-merge integration:
- Add tiered billing (TryTieredSettle, InjectTieredBillingInfo) to PostTextConsumeQuota
- Update tool pricing calls to use nightly's generic GetToolPriceForModel/GetToolPrice API
2026-04-02 00:39:13 +08:00
CaIon 5238f279db feat: record stream interruption reasons via StreamStatus
- Add StreamStatus type (relay/common) to track stream end reason
  (done/timeout/client_gone/scanner_error/eof/panic/ping_fail) and
  accumulate soft errors during streaming via sync.Once + sync.Mutex.
- Add StreamResult (relay/helper) as the callback interface: adapters
  call sr.Error() for soft errors, sr.Stop() for fatal, sr.Done() for
  normal completion. No early-return problem — multiple errors per chunk
  are naturally supported.
- Refactor StreamScannerHandler callback from func(string) bool to
  func(string, *StreamResult). All 9 channel adapters updated.
- Write stream_status into log other JSON field (admin-only) with
  status ok/error, end_reason, error_count, and error messages.
- Frontend: display stream status in log detail expansion for admins.
2026-03-31 16:54:39 +08:00
Seefs e520977efc fix: apply forced beta query at final upstream URL stage 2026-03-19 15:49:50 +08:00
Seefs 8aa8b81e03 fix: original_model && upstream_model paramOverrideKeyAuditPaths 2026-03-17 17:00:01 +08:00
Seefs bc80477b1a feat: simplify param override audit UI and operation labels 2026-03-17 17:00:01 +08:00
Seefs 5db25f47f1 feat: add param override audit modal for usage logs 2026-03-17 17:00:01 +08:00
CaIon 91ed4e196a feat: implement tiered billing expression evaluation and related functionality
- Added support for tiered billing expressions in the billing system.
- Introduced new types and functions for handling billing expressions, including caching and execution.
- Updated existing billing logic to accommodate tiered billing scenarios.
- Enhanced request handling to support incoming billing expression requests.
- Added tests for tiered billing functionality to ensure correctness.
2026-03-17 16:59:25 +08:00
Seefs d087cc5025 feat:support $keep_only_declared and deduped $append for header token overrides 2026-03-09 00:12:53 +08:00
Seefs d2332685db feat: add wildcard path support and improve param override templates/editor 2026-03-05 16:39:34 +08:00
Seefs ad326beb10 Merge pull request #3066 from seefs001/fix/aws-header-override
Fix/aws header override
2026-03-02 18:54:56 +08:00
Seefs 054370abdc feat: support token-map rewrite for comma-separated headers and add bedrock anthropic-beta preset 2026-02-27 19:47:32 +08:00
Seefs a955d4102d refactor(override): simplify header overrides to a lowercase single map 2026-02-25 17:24:18 +08:00
Seefs 3034fb8899 fix: merge runtime and channel header overrides, skip missing source headers 2026-02-25 16:12:34 +08:00
Seefs 58fcd9cbca fix pass_headers 2026-02-25 15:39:49 +08:00
Seefs 0f09dbda2b Merge branch 'upstream-main' into feature/improve-param-override
# Conflicts:
#	relay/channel/api_request_test.go
#	relay/common/override_test.go
#	web/src/components/table/channels/modals/EditChannelModal.jsx
2026-02-25 13:39:54 +08:00
Calcium-Ion 49eb6d3c1e feat: add missing OpenAI/Claude/Gemini request fields (#2971)
* feat: add missing OpenAI/Claude/Gemini request fields and responses stream options

* fix: skip field filtering when request passthrough is enabled

* fix: include subscription in personal sidebar module controls

* feat: gate Claude inference_geo passthrough behind channel setting and add field docs
2026-02-22 23:31:18 +08:00
Seefs 8cfc2b4398 fix: claude affinity cache counter (#2980)
* fix: claude affinity cache counter

* fix: claude affinity cache counter

* fix: stabilize cache usage stats format and simplify modal rendering
2026-02-22 23:30:02 +08:00
Seefs 71886f4e57 feat: add pass_headers op, grouped presets (incl. Gemini 4K), and robust JSON fallback 2026-02-22 17:16:57 +08:00
CaIon 6f39c02857 refactor(relay): improve channel locking and retry logic in RelayTask
- Enhanced the RelayTask function to utilize a locked channel when available, allowing for better reuse during retries.
- Updated error handling to ensure proper context setup for the selected channel.
- Clarified comments in ResolveOriginTask regarding channel locking and retry behavior.
- Introduced a new field in TaskRelayInfo to store the locked channel object, improving type safety and reducing import cycles.
2026-02-22 16:01:19 +08:00
CaIon 8374a83084 feat(task): add adaptor billing interface and async settlement framework
Add three billing lifecycle methods to the TaskAdaptor interface:
- EstimateBilling: compute OtherRatios from user request before pricing
- AdjustBillingOnSubmit: adjust ratios from upstream submit response
- AdjustBillingOnComplete: determine final quota at task terminal state

Introduce BaseBilling as embeddable no-op default for adaptors without
custom billing. Move Sora/Ali OtherRatios logic from shared validation
into per-adaptor EstimateBilling implementations.

Add TaskBillingContext to persist pricing params (model_price, group_ratio,
other_ratios) in task private data for async polling settlement.

Extract RecalculateTaskQuota as a general-purpose delta settlement
function and unify polling billing via settleTaskBillingOnComplete
(adaptor-first, then token-based fallback).
2026-02-22 16:00:27 +08:00
CaIon ba25ba88fe refactor(task): extract billing and polling logic from controller to service layer
Restructure the task relay system for better separation of concerns:
- Extract task billing into service/task_billing.go with unified settlement flow
- Move task polling loop from controller to service/task_polling.go (supports Suno + video platforms)
- Split RelayTask into fetch/submit paths with dedicated retry logic (taskSubmitWithRetry)
- Add TaskDto, TaskResponse generics, and FetchReq to dto/task.go
- Add taskcommon/helpers.go for shared task adaptor utilities
- Remove controller/task_video.go (logic consolidated into service layer)
- Update all task adaptors (ali, doubao, gemini, hailuo, jimeng, kling, sora, suno, vertex, vidu)
- Simplify frontend task logs to use new TaskDto response format
2026-02-22 16:00:27 +08:00
Seefs 768745fd1b feat: sync field 2026-02-22 01:27:58 +08:00
Seefs 7633863c96 feat: unify param/header overrides with retry-aware conditions and flexible header operations 2026-02-22 00:45:49 +08:00
Seefs aebc8ae254 feat: add retry-aware param override with return_error and prune_objects 2026-02-22 00:10:49 +08:00
Seefs bb7ad0ce15 feat: gate Claude inference_geo passthrough behind channel setting and add field docs 2026-02-21 14:25:58 +08:00