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
This commit is contained in:
Calcium-Ion
2026-07-11 20:44:12 +08:00
committed by GitHub
parent 1250fb2eb5
commit c36418c863
106 changed files with 13345 additions and 4307 deletions
+45
View File
@@ -3,14 +3,59 @@ package common
import (
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/QuantumNous/new-api/constant"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSanitizeURLForLogMasksSensitiveQueryValues(t *testing.T) {
rawURL := "https://example.test/v1beta/models/gemini:streamGenerateContent?alt=sse&key=sk-secret&access_token=ya29-secret&api-version=2024-02-01"
got := SanitizeURLForLog(rawURL)
assert.NotContains(t, got, "sk-secret")
assert.NotContains(t, got, "ya29-secret")
parsedURL, err := url.Parse(got)
require.NoError(t, err)
query := parsedURL.Query()
assert.Equal(t, "***masked***", query.Get("key"))
assert.Equal(t, "***masked***", query.Get("access_token"))
assert.Equal(t, "sse", query.Get("alt"))
assert.Equal(t, "2024-02-01", query.Get("api-version"))
}
func TestSanitizeURLForLogMasksAWSAndSecretLikeQueryKeys(t *testing.T) {
rawURL := "https://example.test/path?X-Amz-Credential=credential&X-Amz-Signature=signature&session_token=session&client_secret=secret&model=gpt-test"
got := SanitizeURLForLog(rawURL)
assert.NotContains(t, got, "X-Amz-Credential=credential")
assert.NotContains(t, got, "X-Amz-Signature=signature")
assert.NotContains(t, got, "session_token=session")
assert.NotContains(t, got, "client_secret=secret")
parsedURL, err := url.Parse(got)
require.NoError(t, err)
query := parsedURL.Query()
assert.Equal(t, "***masked***", query.Get("X-Amz-Credential"))
assert.Equal(t, "***masked***", query.Get("X-Amz-Signature"))
assert.Equal(t, "***masked***", query.Get("session_token"))
assert.Equal(t, "***masked***", query.Get("client_secret"))
assert.Equal(t, "gpt-test", query.Get("model"))
}
func TestSanitizeURLForLogKeepsURLWithoutSensitiveQuery(t *testing.T) {
rawURL := "https://example.test/v1/chat/completions?api-version=2024-02-01&alt=sse"
got := SanitizeURLForLog(rawURL)
assert.Equal(t, rawURL, got)
}
func TestValidateMultipartDirectNormalizesImageField(t *testing.T) {
gin.SetMode(gin.TestMode)
body := strings.NewReader(`{"model":"wan2.7-i2v","prompt":"animate","image":" https://example.com/first.png "}`)