feat: sync deployed v1.0.0-rc.38-custom.42

Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
This commit is contained in:
cneicy committed 2026-09-26 04:49:11 +08:00
1 parent aa6c3f50f4
commit ceddfd3d6b
828 files changed
+122253 -13702

No files matched your search

@@ -7,6 +7,7 @@ import (
"github.com/QuantumNous/new-api/relaykit/dto"
"github.com/QuantumNous/new-api/relaykit/relayconvert/convmeta"
"github.com/QuantumNous/new-api/relaykit/relayconvert/internal/convdiag"
relaymedia "github.com/QuantumNous/new-api/relaykit/relayconvert/internal/media"
sharedclaude "github.com/QuantumNous/new-api/relaykit/relayconvert/internal/shared/claude"
kitutil "github.com/QuantumNous/new-api/relaykit/relayconvert/kitutil"
@@ -56,10 +57,11 @@ func OpenAIResponsesRequestToClaudeMessages(c context.Context, info convmeta.Met
if toolChoice != nil || RawJSONPresent(req.ParallelToolCalls) {
claudeRequest.ToolChoice = sharedclaude.MapOpenAIToolChoice(toolChoice, ParallelToolCalls(req.ParallelToolCalls))
}
sourceReasoning, err := reasoning.FromOpenAIResponses(req)
sourceReasoning, diagnostics, err := reasoning.FromOpenAIResponses(req)
if err != nil {
return nil, reasoning.AsClientError(err)
}
convdiag.Add(c, diagnostics...)
if err := sharedclaude.ApplyReasoning(c, claudeRequest, info, sourceReasoning, true); err != nil {
return nil, reasoning.AsClientError(err)
}
@@ -8,6 +8,7 @@ import (
"github.com/QuantumNous/new-api/relaykit/dto"
"github.com/QuantumNous/new-api/relaykit/relayconvert/convmeta"
"github.com/QuantumNous/new-api/relaykit/relayconvert/internal/convdiag"
relaymedia "github.com/QuantumNous/new-api/relaykit/relayconvert/internal/media"
sharedgemini "github.com/QuantumNous/new-api/relaykit/relayconvert/internal/shared/gemini"
kitutil "github.com/QuantumNous/new-api/relaykit/relayconvert/kitutil"
@@ -61,17 +62,18 @@ func OpenAIResponsesRequestToGeminiChat(c context.Context, req *dto.OpenAIRespon
if err := applyResponsesTextToGemini(req.Text, geminiRequest); err != nil {
return nil, err
}
reasoningIntent, err := reasoning.FromOpenAIResponses(req)
reasoningIntent, diagnostics, err := reasoning.FromOpenAIResponses(req)
if err != nil {
return nil, reasoning.AsClientError(err)
}
convdiag.Add(c, diagnostics...)
var reasoningPivot dto.GeneralOpenAIRequest
if err := reasoning.ApplyToOpenAIChat(&reasoningPivot, reasoningIntent); err != nil {
return nil, reasoning.AsClientError(err)
}
reasoningPivot.Model = req.Model
reasoningPivot.MaxCompletionTokens = req.MaxOutputTokens
if err := sharedgemini.ApplyThinkingConfig(geminiRequest, info, reasoningPivot); err != nil {
if err := sharedgemini.ApplyThinkingConfig(c, geminiRequest, info, reasoningPivot); err != nil {
return nil, reasoning.AsClientError(err)
}
@@ -1,12 +1,14 @@
package oairesponses
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/QuantumNous/new-api/relaykit/dto"
"github.com/QuantumNous/new-api/relaykit/relayconvert/internal/convdiag"
kitutil "github.com/QuantumNous/new-api/relaykit/relayconvert/kitutil"
"github.com/QuantumNous/new-api/relaykit/relayconvert/reasoning"
)
@@ -25,7 +27,7 @@ const (
ResponsesInputTypeCustomToolOutput = responsesInputTypeCustomToolOutput
)
func ResponsesRequestToChatCompletionsRequest(req *dto.OpenAIResponsesRequest) (*dto.GeneralOpenAIRequest, error) {
func ResponsesRequestToChatCompletionsRequest(ctx context.Context, req *dto.OpenAIResponsesRequest) (*dto.GeneralOpenAIRequest, error) {
if req == nil {
return nil, errors.New("request is nil")
}
@@ -86,9 +88,12 @@ func ResponsesRequestToChatCompletionsRequest(req *dto.OpenAIResponsesRequest) (
return nil, fmt.Errorf("invalid presence_penalty: %w", err)
}
if reasoningIntent, err := reasoning.FromOpenAIResponses(req); err != nil {
reasoningIntent, diagnostics, err := reasoning.FromOpenAIResponses(req)
if err != nil {
return nil, reasoning.AsClientError(err)
} else if err := reasoning.ApplyToOpenAIChat(out, reasoningIntent); err != nil {
}
convdiag.Add(ctx, diagnostics...)
if err := reasoning.ApplyToOpenAIChat(out, reasoningIntent); err != nil {
return nil, reasoning.AsClientError(err)
}
if req.ServiceTier != "" {
@@ -1,6 +1,7 @@
package oairesponses
import (
"context"
"encoding/json"
"testing"
@@ -19,7 +20,7 @@ func TestResponsesRequestToChatCompletionsRequestInstructionsAndScalarInput(t *t
maxOutputTokens := uint(128)
parallelToolCalls := true
got, err := ResponsesRequestToChatCompletionsRequest(&dto.OpenAIResponsesRequest{
got, err := ResponsesRequestToChatCompletionsRequest(context.Background(), &dto.OpenAIResponsesRequest{
Model: "gpt-test",
Instructions: mustRawMessage(t, "system rules"),
Input: mustRawMessage(t, "hello"),
@@ -68,7 +69,7 @@ func TestResponsesRequestToChatCompletionsRequestPreservesQwenThinkingBudget(t *
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ResponsesRequestToChatCompletionsRequest(&dto.OpenAIResponsesRequest{
got, err := ResponsesRequestToChatCompletionsRequest(context.Background(), &dto.OpenAIResponsesRequest{
Model: "qwen-plus",
Input: mustRawMessage(t, "hello"),
EnableThinking: json.RawMessage(`true`),
@@ -89,7 +90,7 @@ func TestResponsesRequestToChatCompletionsRequestPreservesQwenThinkingBudget(t *
}
func TestResponsesRequestToChatCompletionsRequestMultimodalInput(t *testing.T) {
got, err := ResponsesRequestToChatCompletionsRequest(&dto.OpenAIResponsesRequest{
got, err := ResponsesRequestToChatCompletionsRequest(context.Background(), &dto.OpenAIResponsesRequest{
Model: "gpt-test",
Input: mustRawMessage(t, []map[string]any{
{
@@ -123,7 +124,7 @@ func TestResponsesRequestToChatCompletionsRequestMultimodalInput(t *testing.T) {
}
func TestResponsesRequestToChatCompletionsRequestAssistantTextAndFunctionCallCoexist(t *testing.T) {
got, err := ResponsesRequestToChatCompletionsRequest(&dto.OpenAIResponsesRequest{
got, err := ResponsesRequestToChatCompletionsRequest(context.Background(), &dto.OpenAIResponsesRequest{
Model: "gpt-test",
Input: mustRawMessage(t, []map[string]any{
{
@@ -162,7 +163,7 @@ func TestResponsesRequestToChatCompletionsRequestAssistantTextAndFunctionCallCoe
}
func TestResponsesRequestToChatCompletionsRequestOnlyFunctionCallCreatesAssistant(t *testing.T) {
got, err := ResponsesRequestToChatCompletionsRequest(&dto.OpenAIResponsesRequest{
got, err := ResponsesRequestToChatCompletionsRequest(context.Background(), &dto.OpenAIResponsesRequest{
Model: "gpt-test",
Input: mustRawMessage(t, []map[string]any{
{
@@ -184,7 +185,7 @@ func TestResponsesRequestToChatCompletionsRequestOnlyFunctionCallCreatesAssistan
}
func TestResponsesRequestToChatCompletionsRequestToolsToolChoiceAndTextFormat(t *testing.T) {
got, err := ResponsesRequestToChatCompletionsRequest(&dto.OpenAIResponsesRequest{
got, err := ResponsesRequestToChatCompletionsRequest(context.Background(), &dto.OpenAIResponsesRequest{
Model: "gpt-test",
Input: mustRawMessage(t, "hello"),
Tools: mustRawMessage(t, []map[string]any{
@@ -233,7 +234,7 @@ func TestResponsesRequestToChatCompletionsRequestToolsToolChoiceAndTextFormat(t
}
func TestResponsesRequestToChatCompletionsRequestCustomToolCallPreservesRawShape(t *testing.T) {
got, err := ResponsesRequestToChatCompletionsRequest(&dto.OpenAIResponsesRequest{
got, err := ResponsesRequestToChatCompletionsRequest(context.Background(), &dto.OpenAIResponsesRequest{
Model: "gpt-test",
Input: mustRawMessage(t, []map[string]any{
{
@@ -287,7 +288,7 @@ func TestResponsesRequestToChatCompletionsRequestRejectsStatefulFields(t *testin
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := ResponsesRequestToChatCompletionsRequest(tt.req)
_, err := ResponsesRequestToChatCompletionsRequest(context.Background(), tt.req)
require.Error(t, err)
assert.Contains(t, err.Error(), tt.want)
assert.Contains(t, err.Error(), "stateful fields")
@@ -326,7 +327,7 @@ func TestResponsesRequestToChatCompletionsRequestPreservesPenalties(t *testing.T
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ResponsesRequestToChatCompletionsRequest(&dto.OpenAIResponsesRequest{
got, err := ResponsesRequestToChatCompletionsRequest(context.Background(), &dto.OpenAIResponsesRequest{
Model: "gpt-test",
Input: mustRawMessage(t, "hello"),
FrequencyPenalty: tt.frequencyRaw,
@@ -341,7 +342,7 @@ func TestResponsesRequestToChatCompletionsRequestPreservesPenalties(t *testing.T
}
func TestResponsesRequestToChatCompletionsRequestRejectsMalformedPenalty(t *testing.T) {
_, err := ResponsesRequestToChatCompletionsRequest(&dto.OpenAIResponsesRequest{
_, err := ResponsesRequestToChatCompletionsRequest(context.Background(), &dto.OpenAIResponsesRequest{
Model: "gpt-test",
Input: mustRawMessage(t, "hello"),
FrequencyPenalty: json.RawMessage(`"not-a-number"`),
@@ -66,7 +66,7 @@ func ResponsesResponseToChatCompletionsResponse(resp *dto.OpenAIResponsesRespons
usage := UsageFromResponsesUsage(resp.Usage)
created := resp.CreatedAt
created := int64(resp.CreatedAt)
var toolCalls []dto.ToolCallResponse
if len(resp.Output) > 0 {
@@ -218,12 +218,7 @@ func usageFromResponsesUsage(src *dto.Usage, createBillingSnapshot bool) *dto.Us
usage.TotalTokens = usage.PromptTokens + usage.CompletionTokens
}
if src.InputTokensDetails != nil {
usage.PromptTokensDetails.CachedTokens = src.InputTokensDetails.CachedTokens
usage.PromptTokensDetails.CachedCreationTokens = src.InputTokensDetails.CachedCreationTokens
usage.PromptTokensDetails.CacheWriteTokens = src.InputTokensDetails.CacheWriteTokens
usage.PromptTokensDetails.TextTokens = src.InputTokensDetails.TextTokens
usage.PromptTokensDetails.ImageTokens = src.InputTokensDetails.ImageTokens
usage.PromptTokensDetails.AudioTokens = src.InputTokensDetails.AudioTokens
usage.PromptTokensDetails = src.InputTokensDetails.Clone()
}
if src.CompletionTokenDetails.ReasoningTokens != 0 ||
src.CompletionTokenDetails.TextTokens != 0 ||
@@ -9,6 +9,49 @@ import (
"github.com/stretchr/testify/require"
)
func TestNormalizeResponsesUsagePreservesImageCacheDetails(t *testing.T) {
for _, tc := range []struct {
name string
imageTokens int
}{
{name: "image cache", imageTokens: 200},
{name: "explicit zero image cache", imageTokens: 0},
} {
t.Run(tc.name, func(t *testing.T) {
imageTokens, audioTokens := tc.imageTokens, 0
source := &dto.Usage{
InputTokens: 1000,
OutputTokens: 100,
InputTokensDetails: &dto.InputTokenDetails{
CachedTokens: 300,
CachedCreationTokens: 20,
CacheWriteTokens: 10,
TextTokens: 400,
ImageTokens: 600,
CachedTokensDetails: &dto.CachedTokenDetails{
ImageTokens: &imageTokens,
AudioTokens: &audioTokens,
},
},
}
usage := NormalizeResponsesUsage(source)
assert.Equal(t, *source.InputTokensDetails, usage.PromptTokensDetails)
details := usage.PromptTokensDetails.CachedTokensDetails
require.NotNil(t, details)
require.NotNil(t, details.ImageTokens)
require.NotNil(t, details.AudioTokens)
assert.Nil(t, details.TextTokens, "missing modalities must remain absent")
imageTokens, audioTokens = 999, 888
source.InputTokensDetails.ImageTokens = 9999
assert.Equal(t, tc.imageTokens, *details.ImageTokens, "later source updates must not change billable image cache")
assert.Zero(t, *details.AudioTokens, "explicit zero must survive normalization and source mutation")
assert.Equal(t, 600, usage.PromptTokensDetails.ImageTokens)
})
}
}
func TestResponsesResponseToChatCompletionsPreservesTextAndToolCalls(t *testing.T) {
resp := &dto.OpenAIResponsesResponse{
ID: "resp_1",