merge: 合并官方 upstream/main 的 6 个功能更新
Some checks failed
CI / test (push) Has been cancelled
CI / golangci-lint (push) Has been cancelled
CI / test (pull_request) Has been cancelled
CI / golangci-lint (pull_request) Has been cancelled

合并内容:
1. feat(gateway): Claude Code 系统提示词智能注入
2. fix: 修复创建账号 schedulable 默认值为 false 的 bug
3. fix(frontend): 修复跨时区日期范围筛选问题
4. feat(proxy): SOCKS5H 代理支持(统一代理配置)
5. fix(oauth): 修复 Claude Cookie 添加账号时会话混淆
6. fix(test): 修复 OAuth 账号测试刷新 token 的 bug

新增文件:
- backend/internal/pkg/proxyutil/* (SOCKS5H 支持)
- backend/internal/service/gateway_prompt_test.go (测试)

来自 upstream: Wei-Shaw/sub2api commits d9b1587..a527559
This commit is contained in:
huangzhenpc
2026-01-04 18:48:05 +08:00
18 changed files with 10346 additions and 9797 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,233 @@
package service
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
)
func TestIsClaudeCodeClient(t *testing.T) {
tests := []struct {
name string
userAgent string
metadataUserID string
want bool
}{
{
name: "Claude Code client",
userAgent: "claude-cli/1.0.62 (darwin; arm64)",
metadataUserID: "session_123e4567-e89b-12d3-a456-426614174000",
want: true,
},
{
name: "Claude Code without version suffix",
userAgent: "claude-cli/2.0.0",
metadataUserID: "session_abc",
want: true,
},
{
name: "Missing metadata user_id",
userAgent: "claude-cli/1.0.0",
metadataUserID: "",
want: false,
},
{
name: "Different user agent",
userAgent: "curl/7.68.0",
metadataUserID: "user123",
want: false,
},
{
name: "Empty user agent",
userAgent: "",
metadataUserID: "user123",
want: false,
},
{
name: "Similar but not Claude CLI",
userAgent: "claude-api/1.0.0",
metadataUserID: "user123",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := isClaudeCodeClient(tt.userAgent, tt.metadataUserID)
require.Equal(t, tt.want, got)
})
}
}
func TestSystemIncludesClaudeCodePrompt(t *testing.T) {
tests := []struct {
name string
system any
want bool
}{
{
name: "nil system",
system: nil,
want: false,
},
{
name: "empty string",
system: "",
want: false,
},
{
name: "string with Claude Code prompt",
system: claudeCodeSystemPrompt,
want: true,
},
{
name: "string with different content",
system: "You are a helpful assistant.",
want: false,
},
{
name: "empty array",
system: []any{},
want: false,
},
{
name: "array with Claude Code prompt",
system: []any{
map[string]any{
"type": "text",
"text": claudeCodeSystemPrompt,
},
},
want: true,
},
{
name: "array with Claude Code prompt in second position",
system: []any{
map[string]any{"type": "text", "text": "First prompt"},
map[string]any{"type": "text", "text": claudeCodeSystemPrompt},
},
want: true,
},
{
name: "array without Claude Code prompt",
system: []any{
map[string]any{"type": "text", "text": "Custom prompt"},
},
want: false,
},
{
name: "array with partial match (should not match)",
system: []any{
map[string]any{"type": "text", "text": "You are Claude"},
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := systemIncludesClaudeCodePrompt(tt.system)
require.Equal(t, tt.want, got)
})
}
}
func TestInjectClaudeCodePrompt(t *testing.T) {
tests := []struct {
name string
body string
system any
wantSystemLen int
wantFirstText string
wantSecondText string
}{
{
name: "nil system",
body: `{"model":"claude-3"}`,
system: nil,
wantSystemLen: 1,
wantFirstText: claudeCodeSystemPrompt,
},
{
name: "empty string system",
body: `{"model":"claude-3"}`,
system: "",
wantSystemLen: 1,
wantFirstText: claudeCodeSystemPrompt,
},
{
name: "string system",
body: `{"model":"claude-3"}`,
system: "Custom prompt",
wantSystemLen: 2,
wantFirstText: claudeCodeSystemPrompt,
wantSecondText: "Custom prompt",
},
{
name: "string system equals Claude Code prompt",
body: `{"model":"claude-3"}`,
system: claudeCodeSystemPrompt,
wantSystemLen: 1,
wantFirstText: claudeCodeSystemPrompt,
},
{
name: "array system",
body: `{"model":"claude-3"}`,
system: []any{map[string]any{"type": "text", "text": "Custom"}},
// Claude Code + Custom = 2
wantSystemLen: 2,
wantFirstText: claudeCodeSystemPrompt,
wantSecondText: "Custom",
},
{
name: "array system with existing Claude Code prompt (should dedupe)",
body: `{"model":"claude-3"}`,
system: []any{
map[string]any{"type": "text", "text": claudeCodeSystemPrompt},
map[string]any{"type": "text", "text": "Other"},
},
// Claude Code at start + Other = 2 (deduped)
wantSystemLen: 2,
wantFirstText: claudeCodeSystemPrompt,
wantSecondText: "Other",
},
{
name: "empty array",
body: `{"model":"claude-3"}`,
system: []any{},
wantSystemLen: 1,
wantFirstText: claudeCodeSystemPrompt,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := injectClaudeCodePrompt([]byte(tt.body), tt.system)
var parsed map[string]any
err := json.Unmarshal(result, &parsed)
require.NoError(t, err)
system, ok := parsed["system"].([]any)
require.True(t, ok, "system should be an array")
require.Len(t, system, tt.wantSystemLen)
first, ok := system[0].(map[string]any)
require.True(t, ok)
require.Equal(t, tt.wantFirstText, first["text"])
require.Equal(t, "text", first["type"])
// Check cache_control
cc, ok := first["cache_control"].(map[string]any)
require.True(t, ok)
require.Equal(t, "ephemeral", cc["type"])
if tt.wantSecondText != "" && len(system) > 1 {
second, ok := system[1].(map[string]any)
require.True(t, ok)
require.Equal(t, tt.wantSecondText, second["text"])
}
})
}
}

File diff suppressed because it is too large Load Diff