feat: publish sanitized custom.97 source

This commit is contained in:
cneicy committed 2026-10-27 20:43:39 +08:00
1 parent 8e6ed0317b
commit 86c0276156
1022 files changed
+69903 -19207

No files matched your search

+98
View File
@@ -1,15 +1,20 @@
package claude
import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/constant"
relaycommon "github.com/QuantumNous/new-api/relay/common"
"github.com/QuantumNous/new-api/relay/helper"
"github.com/QuantumNous/new-api/relaykit/dto"
"github.com/QuantumNous/new-api/relaykit/relayconvert"
"github.com/QuantumNous/new-api/relaykit/types"
"github.com/QuantumNous/new-api/tokenkit"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -19,6 +24,47 @@ func commonPointer[T any](value T) *T {
return &value
}
func TestClaudeStreamFinalUsageEstimatesOnlyUncountedUsage(t *testing.T) {
gin.SetMode(gin.TestMode)
for _, tc := range []struct {
name, startUsage, deltaUsage, stopReason string // no deltaUsage: the stream broke before message_delta
streamText bool
wantInput, wantOutput int
wantLocalCount bool
}{
{name: "cache-only refusal keeps zeros", startUsage: `{"input_tokens":0,"output_tokens":0,"cache_creation_input_tokens":20000,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":20000}}`, deltaUsage: `{"output_tokens":0}`, stopReason: "refusal"},
{name: "broken stream keeps cache-only input", startUsage: `{"input_tokens":0,"output_tokens":1,"cache_read_input_tokens":50000}`, streamText: true, wantOutput: 2, wantLocalCount: true},
{name: "zeros after streamed text are estimated", startUsage: `{"input_tokens":0,"output_tokens":0}`, deltaUsage: `{"output_tokens":0}`, stopReason: "end_turn", streamText: true, wantInput: 1200, wantOutput: 2, wantLocalCount: true},
{name: "zero delta input keeps start input", startUsage: `{"input_tokens":1000,"output_tokens":1}`, deltaUsage: `{"input_tokens":0,"output_tokens":50}`, stopReason: "end_turn", streamText: true, wantInput: 1000, wantOutput: 50},
} {
t.Run(tc.name, func(t *testing.T) {
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request = httptest.NewRequest(http.MethodPost, "/v1/messages", nil)
info := &relaycommon.RelayInfo{RelayFormat: types.RelayFormatClaude, ChannelMeta: &relaycommon.ChannelMeta{UpstreamModelName: "claude-opus-4-8"}}
info.SetEstimatePromptTokens(1200)
state := &ClaudeResponseInfo{Usage: &dto.Usage{}}
start := fmt.Sprintf(`{"type":"message_start","message":{"id":"msg_1","model":"claude-opus-4-8","usage":%s}}`, tc.startUsage)
require.Nil(t, HandleStreamResponseData(c, info, state, start))
if tc.streamText {
require.Nil(t, HandleStreamResponseData(c, info, state, `{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"hello"}}`))
}
if tc.deltaUsage != "" {
delta := fmt.Sprintf(`{"type":"message_delta","delta":{"stop_reason":%q},"usage":%s}`, tc.stopReason, tc.deltaUsage)
require.Nil(t, HandleStreamResponseData(c, info, state, delta))
require.Nil(t, HandleStreamResponseData(c, info, state, `{"type":"message_stop"}`))
}
HandleStreamFinalResponse(c, info, state)
assert.Equal(t, tc.wantInput, state.Usage.PromptTokens)
assert.Equal(t, tc.wantOutput, state.Usage.CompletionTokens)
assert.Equal(t, tc.wantLocalCount, common.GetContextKeyBool(c, constant.ContextKeyLocalCountTokens))
canonical, ok := state.Usage.BillingUsage.CanonicalUsage()
require.True(t, ok)
assert.Equal(t, tc.wantInput, canonical.PromptTokens, "settlement must bill the same input")
assert.Equal(t, tc.wantOutput, canonical.CompletionTokens)
})
}
}
func TestResponseOpenAI2ClaudeToolUseInputIsObject(t *testing.T) {
tests := []struct {
name string
@@ -228,6 +274,33 @@ func TestFormatClaudeResponseInfo_ContentBlockDelta(t *testing.T) {
}
}
// A stream cut off during an agent tool call must still estimate the call's
// output tokens when the final usage never arrives.
func TestClaudeStreamFallbackCountsInterruptedToolUse(t *testing.T) {
claudeInfo := &ClaudeResponseInfo{Usage: &dto.Usage{}}
events := []*dto.ClaudeResponse{
{Type: "content_block_start", ContentBlock: &dto.ClaudeMediaMessage{Type: "tool_use", Id: "toolu_1", Name: "read_file"}},
{Type: "content_block_delta", Delta: &dto.ClaudeMediaMessage{Type: "input_json_delta", PartialJson: commonPointer(`{"path":`)}},
{Type: "content_block_delta", Delta: &dto.ClaudeMediaMessage{Type: "input_json_delta", PartialJson: commonPointer(`"main.go"}`)}},
}
for _, event := range events {
require.True(t, FormatClaudeResponseInfo(event, nil, claudeInfo))
}
require.Equal(t, `read_file{"path":"main.go"}`, claudeInfo.ResponseText.String())
c, _ := gin.CreateTestContext(httptest.NewRecorder())
info := &relaycommon.RelayInfo{
RelayFormat: types.RelayFormatClaude,
ChannelMeta: &relaycommon.ChannelMeta{UpstreamModelName: "claude-sonnet-5"},
}
info.SetEstimatePromptTokens(100)
HandleStreamFinalResponse(c, info, claudeInfo)
// Text estimate plus the per-tool_use framing the upstream bills.
assert.Equal(t, tokenkit.Estimate("claude-sonnet-5", `read_file{"path":"main.go"}`)+40, claudeInfo.Usage.CompletionTokens)
assert.Equal(t, 100, claudeInfo.Usage.PromptTokens)
}
func TestBuildOpenAIStyleUsageFromClaudeUsage(t *testing.T) {
usage := &dto.Usage{
PromptTokens: 100,
@@ -400,3 +473,28 @@ func TestOpenAIChatRequestToClaudeMessages_ClaudeOpus48ThinkingUsesAdaptiveHighE
require.Nil(t, claudeRequest.TopP)
require.Nil(t, claudeRequest.TopK)
}
func TestClaudeResponsePassesInputTransformationsThrough(t *testing.T) {
gin.SetMode(gin.TestMode)
info := &relaycommon.RelayInfo{
RelayFormat: types.RelayFormatClaude,
ChannelMeta: &relaycommon.ChannelMeta{UpstreamModelName: "claude-opus-5-5"},
}
transformations := `"input_transformations":[{"type":"thinking_dropped","path":"messages.1.content.0","reason":"prefix_binding_mismatch"}]`
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Request = httptest.NewRequest("POST", "/v1/messages", nil)
body := `{"id":"msg_1","type":"message","role":"assistant","model":"claude-opus-5-5","content":[{"type":"text","text":"hi"}],` +
`"stop_reason":"end_turn",` + transformations + `,"usage":{"input_tokens":5,"output_tokens":1}}`
require.Nil(t, HandleClaudeResponseData(c, info, &ClaudeResponseInfo{Usage: &dto.Usage{}}, nil, []byte(body)))
assert.Equal(t, body, w.Body.String())
w = httptest.NewRecorder()
c, _ = gin.CreateTestContext(w)
c.Request = httptest.NewRequest("POST", "/v1/messages", nil)
event := `{"type":"message_start","message":{"id":"msg_1","type":"message","role":"assistant","model":"claude-opus-5-5","content":[],` +
transformations + `,"usage":{"input_tokens":5,"output_tokens":1}}}`
require.Nil(t, HandleStreamResponseData(c, info, &ClaudeResponseInfo{Usage: &dto.Usage{}}, event))
assert.Contains(t, w.Body.String(), "data: "+event+"\n")
}