chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:35:43 +08:00
commit 0e2cc6b102
927 changed files with 260929 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
package test
import (
"testing"
_ "github.com/router-for-me/CLIProxyAPI/v7/internal/translator"
sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator"
"github.com/tidwall/gjson"
)
func TestOpenAIToCodex_PreservesBuiltinTools(t *testing.T) {
in := []byte(`{
"model":"gpt-5",
"messages":[{"role":"user","content":"hi"}],
"tools":[{"type":"web_search","search_context_size":"high"}],
"tool_choice":{"type":"web_search"}
}`)
out := sdktranslator.TranslateRequest(sdktranslator.FormatOpenAI, sdktranslator.FormatCodex, "gpt-5", in, false)
if got := gjson.GetBytes(out, "tools.#").Int(); got != 1 {
t.Fatalf("expected 1 tool, got %d: %s", got, string(out))
}
if got := gjson.GetBytes(out, "tools.0.type").String(); got != "web_search" {
t.Fatalf("expected tools[0].type=web_search, got %q: %s", got, string(out))
}
if got := gjson.GetBytes(out, "tools.0.search_context_size").String(); got != "high" {
t.Fatalf("expected tools[0].search_context_size=high, got %q: %s", got, string(out))
}
if got := gjson.GetBytes(out, "tool_choice.type").String(); got != "web_search" {
t.Fatalf("expected tool_choice.type=web_search, got %q: %s", got, string(out))
}
}
func TestOpenAIResponsesToOpenAI_IgnoresBuiltinTools(t *testing.T) {
in := []byte(`{
"model":"gpt-5",
"input":[{"role":"user","content":[{"type":"input_text","text":"hi"}]}],
"tools":[{"type":"web_search","search_context_size":"low"}]
}`)
out := sdktranslator.TranslateRequest(sdktranslator.FormatOpenAIResponse, sdktranslator.FormatOpenAI, "gpt-5", in, false)
if got := gjson.GetBytes(out, "tools.#").Int(); got != 0 {
t.Fatalf("expected 0 tools (builtin tools not supported in Chat Completions), got %d: %s", got, string(out))
}
}
@@ -0,0 +1,106 @@
package test
import (
"encoding/json"
"os"
"path/filepath"
"testing"
)
type jsonObject = map[string]any
func loadClaudeCodeSentinelFixture(t *testing.T, name string) jsonObject {
t.Helper()
path := filepath.Join("testdata", "claude_code_sentinels", name)
data := mustReadFile(t, path)
var payload jsonObject
if err := json.Unmarshal(data, &payload); err != nil {
t.Fatalf("unmarshal %s: %v", name, err)
}
return payload
}
func mustReadFile(t *testing.T, path string) []byte {
t.Helper()
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
return data
}
func requireStringField(t *testing.T, obj jsonObject, key string) string {
t.Helper()
value, ok := obj[key].(string)
if !ok || value == "" {
t.Fatalf("field %q missing or empty: %#v", key, obj[key])
}
return value
}
func TestClaudeCodeSentinel_ToolProgressShape(t *testing.T) {
payload := loadClaudeCodeSentinelFixture(t, "tool_progress.json")
if got := requireStringField(t, payload, "type"); got != "tool_progress" {
t.Fatalf("type = %q, want tool_progress", got)
}
requireStringField(t, payload, "tool_use_id")
requireStringField(t, payload, "tool_name")
requireStringField(t, payload, "session_id")
if _, ok := payload["elapsed_time_seconds"].(float64); !ok {
t.Fatalf("elapsed_time_seconds missing or non-number: %#v", payload["elapsed_time_seconds"])
}
}
func TestClaudeCodeSentinel_SessionStateShape(t *testing.T) {
payload := loadClaudeCodeSentinelFixture(t, "session_state_changed.json")
if got := requireStringField(t, payload, "type"); got != "system" {
t.Fatalf("type = %q, want system", got)
}
if got := requireStringField(t, payload, "subtype"); got != "session_state_changed" {
t.Fatalf("subtype = %q, want session_state_changed", got)
}
state := requireStringField(t, payload, "state")
switch state {
case "idle", "running", "requires_action":
default:
t.Fatalf("unexpected session state %q", state)
}
requireStringField(t, payload, "session_id")
}
func TestClaudeCodeSentinel_ToolUseSummaryShape(t *testing.T) {
payload := loadClaudeCodeSentinelFixture(t, "tool_use_summary.json")
if got := requireStringField(t, payload, "type"); got != "tool_use_summary" {
t.Fatalf("type = %q, want tool_use_summary", got)
}
requireStringField(t, payload, "summary")
rawIDs, ok := payload["preceding_tool_use_ids"].([]any)
if !ok || len(rawIDs) == 0 {
t.Fatalf("preceding_tool_use_ids missing or empty: %#v", payload["preceding_tool_use_ids"])
}
for i, raw := range rawIDs {
if id, ok := raw.(string); !ok || id == "" {
t.Fatalf("preceding_tool_use_ids[%d] invalid: %#v", i, raw)
}
}
}
func TestClaudeCodeSentinel_ControlRequestCanUseToolShape(t *testing.T) {
payload := loadClaudeCodeSentinelFixture(t, "control_request_can_use_tool.json")
if got := requireStringField(t, payload, "type"); got != "control_request" {
t.Fatalf("type = %q, want control_request", got)
}
requireStringField(t, payload, "request_id")
request, ok := payload["request"].(map[string]any)
if !ok {
t.Fatalf("request missing or invalid: %#v", payload["request"])
}
if got := requireStringField(t, request, "subtype"); got != "can_use_tool" {
t.Fatalf("request.subtype = %q, want can_use_tool", got)
}
requireStringField(t, request, "tool_name")
requireStringField(t, request, "tool_use_id")
if input, ok := request["input"].(map[string]any); !ok || len(input) == 0 {
t.Fatalf("request.input missing or empty: %#v", request["input"])
}
}
@@ -0,0 +1,11 @@
{
"type": "control_request",
"request_id": "req_123",
"request": {
"subtype": "can_use_tool",
"tool_name": "Bash",
"input": {"command": "npm test"},
"tool_use_id": "toolu_123",
"description": "Running npm test"
}
}
@@ -0,0 +1,7 @@
{
"type": "system",
"subtype": "session_state_changed",
"state": "requires_action",
"uuid": "22222222-2222-4222-8222-222222222222",
"session_id": "sess_123"
}
+10
View File
@@ -0,0 +1,10 @@
{
"type": "tool_progress",
"tool_use_id": "toolu_123",
"tool_name": "Bash",
"parent_tool_use_id": null,
"elapsed_time_seconds": 2.5,
"task_id": "task_123",
"uuid": "11111111-1111-4111-8111-111111111111",
"session_id": "sess_123"
}
@@ -0,0 +1,7 @@
{
"type": "tool_use_summary",
"summary": "Searched in auth/",
"preceding_tool_use_ids": ["toolu_1", "toolu_2"],
"uuid": "33333333-3333-4333-8333-333333333333",
"session_id": "sess_123"
}
File diff suppressed because it is too large Load Diff
+122
View File
@@ -0,0 +1,122 @@
package test
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
"github.com/router-for-me/CLIProxyAPI/v7/internal/redisqueue"
runtimeexecutor "github.com/router-for-me/CLIProxyAPI/v7/internal/runtime/executor"
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/auth"
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v7/sdk/cliproxy/executor"
sdktranslator "github.com/router-for-me/CLIProxyAPI/v7/sdk/translator"
)
func TestGeminiExecutorRecordsSuccessfulZeroUsageInQueue(t *testing.T) {
model := fmt.Sprintf("gemini-2.5-flash-zero-usage-%d", time.Now().UnixNano())
source := fmt.Sprintf("zero-usage-%d@example.com", time.Now().UnixNano())
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
wantPath := "/v1beta/models/" + model + ":generateContent"
if r.URL.Path != wantPath {
t.Fatalf("path = %q, want %q", r.URL.Path, wantPath)
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"candidates":[{"content":{"role":"model","parts":[{"text":"ok"}]},"finishReason":"STOP"}],"usageMetadata":{"promptTokenCount":0,"candidatesTokenCount":0,"totalTokenCount":0}}`))
}))
defer server.Close()
executor := runtimeexecutor.NewGeminiExecutor(&config.Config{})
auth := &cliproxyauth.Auth{
Provider: "gemini",
Attributes: map[string]string{
"api_key": "test-upstream-key",
"base_url": server.URL,
},
Metadata: map[string]any{
"email": source,
},
}
prevQueueEnabled := redisqueue.Enabled()
prevUsageEnabled := redisqueue.UsageStatisticsEnabled()
redisqueue.SetEnabled(false)
redisqueue.SetEnabled(true)
redisqueue.SetUsageStatisticsEnabled(true)
t.Cleanup(func() {
redisqueue.SetEnabled(false)
redisqueue.SetEnabled(prevQueueEnabled)
redisqueue.SetUsageStatisticsEnabled(prevUsageEnabled)
})
_, err := executor.Execute(context.Background(), auth, cliproxyexecutor.Request{
Model: model,
Payload: []byte(`{"contents":[{"role":"user","parts":[{"text":"hi"}]}]}`),
}, cliproxyexecutor.Options{
SourceFormat: sdktranslator.FormatGemini,
OriginalRequest: []byte(`{"contents":[{"role":"user","parts":[{"text":"hi"}]}]}`),
})
if err != nil {
t.Fatalf("Execute error: %v", err)
}
waitForQueuedUsageModelTotalTokens(t, "gemini", model, 0)
}
func waitForQueuedUsageModelTotalTokens(t *testing.T, wantProvider, wantModel string, wantTokens int64) {
t.Helper()
deadline := time.Now().Add(2 * time.Second)
for time.Now().Before(deadline) {
items := redisqueue.PopOldest(10)
for _, item := range items {
got, ok := parseQueuedUsagePayload(t, item)
if !ok {
continue
}
if got.Provider != wantProvider || got.Model != wantModel {
continue
}
if got.Failed {
t.Fatalf("payload failed = true, want false")
}
if got.Tokens.TotalTokens != wantTokens {
t.Fatalf("payload total tokens = %d, want %d", got.Tokens.TotalTokens, wantTokens)
}
return
}
time.Sleep(10 * time.Millisecond)
}
t.Fatalf("timed out waiting for queued usage payload for provider=%q model=%q", wantProvider, wantModel)
}
type queuedUsagePayload struct {
Provider string `json:"provider"`
Model string `json:"model"`
Failed bool `json:"failed"`
Tokens struct {
TotalTokens int64 `json:"total_tokens"`
} `json:"tokens"`
}
func parseQueuedUsagePayload(t *testing.T, payload []byte) (queuedUsagePayload, bool) {
t.Helper()
var parsed queuedUsagePayload
if len(payload) == 0 {
return parsed, false
}
if err := json.Unmarshal(payload, &parsed); err != nil {
return parsed, false
}
if parsed.Provider == "" || parsed.Model == "" {
return parsed, false
}
return parsed, true
}