mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-10-21 23:23:13 +08:00
feat(config): preserve unknown nested fields as comments during v8 migration
- Traverse nested configuration paths to comment out unrecognized fields during v8 migration - Remove deprecated Codex `identity-confuse` configuration option and request handling - Clean up identity confuse mapping logic from Codex HTTP and WebSocket executors
This commit is contained in:
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"github.com/router-for-me/CLIProxyAPI/v8/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v8/internal/runtime/executor/helps"
|
||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v8/sdk/cliproxy/auth"
|
||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v8/sdk/cliproxy/executor"
|
||||
@@ -31,7 +30,7 @@ func TestCodexExecutorCacheHelper_OpenAIChatCompletions_StablePromptCacheKeyFrom
|
||||
}
|
||||
url := "https://example.com/responses"
|
||||
|
||||
httpReq, _, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("openai"), url, nil, req, req.Payload, rawJSON)
|
||||
httpReq, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("openai"), url, req, rawJSON)
|
||||
if err != nil {
|
||||
t.Fatalf("cacheHelper error: %v", err)
|
||||
}
|
||||
@@ -56,7 +55,7 @@ func TestCodexExecutorCacheHelper_OpenAIChatCompletions_StablePromptCacheKeyFrom
|
||||
t.Fatalf("Session_id = %q, want empty", gotLegacySession)
|
||||
}
|
||||
|
||||
httpReq2, _, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("openai"), url, nil, req, req.Payload, rawJSON)
|
||||
httpReq2, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("openai"), url, req, rawJSON)
|
||||
if err != nil {
|
||||
t.Fatalf("cacheHelper error (second call): %v", err)
|
||||
}
|
||||
@@ -81,7 +80,7 @@ func TestCodexExecutorCacheHelper_UsesDerivedSessionUUID(t *testing.T) {
|
||||
}
|
||||
expectedKey := helps.DerivedSessionUUID("codex", req.Metadata)
|
||||
|
||||
httpReq, body, _, err := executor.cacheHelper(context.Background(), sdktranslator.FormatOpenAI, "https://example.com/responses", nil, req, req.Payload, []byte(`{"model":"gpt-5.4","stream":true}`))
|
||||
httpReq, body, err := executor.cacheHelper(context.Background(), sdktranslator.FormatOpenAI, "https://example.com/responses", req, []byte(`{"model":"gpt-5.4","stream":true}`))
|
||||
if err != nil {
|
||||
t.Fatalf("cacheHelper error: %v", err)
|
||||
}
|
||||
@@ -118,11 +117,11 @@ func TestCodexExecutorCacheHelper_ClaudeUsesClaudeCodeSessionID(t *testing.T) {
|
||||
}`),
|
||||
}
|
||||
|
||||
firstHTTPReq, _, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("claude"), url, nil, firstReq, firstReq.Payload, rawJSON)
|
||||
firstHTTPReq, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("claude"), url, firstReq, rawJSON)
|
||||
if err != nil {
|
||||
t.Fatalf("cacheHelper first error: %v", err)
|
||||
}
|
||||
secondHTTPReq, _, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("claude"), url, nil, secondReq, secondReq.Payload, rawJSON)
|
||||
secondHTTPReq, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("claude"), url, secondReq, rawJSON)
|
||||
if err != nil {
|
||||
t.Fatalf("cacheHelper second error: %v", err)
|
||||
}
|
||||
@@ -158,7 +157,7 @@ func TestCodexExecutorCacheHelper_ClaudeRejectsBareUserID(t *testing.T) {
|
||||
Payload: []byte(`{"model":"gpt-5.4","metadata":{"user_id":"same-user-across-chats"},"messages":[{"role":"user","content":[{"type":"text","text":"first"}]}]}`),
|
||||
}
|
||||
|
||||
httpReq, _, _, err := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), "https://example.com/responses", nil, req, req.Payload, []byte(`{"model":"gpt-5.4","stream":true}`))
|
||||
httpReq, _, err := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), "https://example.com/responses", req, []byte(`{"model":"gpt-5.4","stream":true}`))
|
||||
if err != nil {
|
||||
t.Fatalf("cacheHelper error: %v", err)
|
||||
}
|
||||
@@ -178,81 +177,6 @@ func TestCodexExecutorCacheHelper_ClaudeRejectsBareUserID(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexExecutorCacheHelper_IdentityConfuseRemapsBodyAndHeaders(t *testing.T) {
|
||||
recorder := httptest.NewRecorder()
|
||||
ginCtx, _ := gin.CreateTestContext(recorder)
|
||||
ginCtx.Request = httptest.NewRequest("POST", "/v1/responses", nil)
|
||||
ginCtx.Request.Header.Set("X-Codex-Turn-Metadata", `{"prompt_cache_key":"cache-1","turn_id":"turn-1","window_id":"cache-1:0"}`)
|
||||
ginCtx.Request.Header.Set("X-Client-Request-Id", "client-request-1")
|
||||
|
||||
ctx := context.WithValue(context.Background(), "gin", ginCtx)
|
||||
executor := &CodexExecutor{cfg: &config.Config{
|
||||
Routing: config.RoutingConfig{Strategy: "fill-first"},
|
||||
Codex: config.CodexConfig{IdentityConfuse: true},
|
||||
}}
|
||||
auth := &cliproxyauth.Auth{ID: "auth-1", Provider: "codex"}
|
||||
rawJSON := []byte(`{"model":"gpt-5-codex","stream":true,"client_metadata":{"x-codex-turn-metadata":"{\"prompt_cache_key\":\"cache-1\",\"turn_id\":\"turn-1\",\"window_id\":\"cache-1:0\"}","x-codex-window-id":"cache-1:0"}}`)
|
||||
req := cliproxyexecutor.Request{
|
||||
Model: "gpt-5-codex",
|
||||
Payload: []byte(`{"model":"gpt-5-codex","prompt_cache_key":"cache-1","client_metadata":{"x-codex-installation-id":"install-1"}}`),
|
||||
}
|
||||
url := "https://example.com/responses"
|
||||
|
||||
httpReq, body, identityState, err := executor.cacheHelper(ctx, sdktranslator.FromString("openai-response"), url, auth, req, req.Payload, rawJSON)
|
||||
if err != nil {
|
||||
t.Fatalf("cacheHelper error: %v", err)
|
||||
}
|
||||
applyCodexHeaders(httpReq, auth, "oauth-token", true, executor.cfg)
|
||||
applyCodexIdentityConfuseHeaders(httpReq.Header, &identityState)
|
||||
|
||||
expectedPromptCacheKey := codexIdentityConfuseUUID("auth-1", "prompt-cache", "cache-1")
|
||||
expectedTurnID := codexIdentityConfuseUUID("auth-1", "turn", "turn-1")
|
||||
if gotKey := gjson.GetBytes(body, "prompt_cache_key").String(); gotKey != expectedPromptCacheKey {
|
||||
t.Fatalf("prompt_cache_key = %q, want %q", gotKey, expectedPromptCacheKey)
|
||||
}
|
||||
expectedInstallationID := codexIdentityConfuseUUID("auth-1", "installation", "install-1")
|
||||
if gotID := gjson.GetBytes(body, "client_metadata.x-codex-installation-id").String(); gotID != expectedInstallationID {
|
||||
t.Fatalf("installation id = %q, want %q", gotID, expectedInstallationID)
|
||||
}
|
||||
gotBodyMetadata := gjson.GetBytes(body, "client_metadata.x-codex-turn-metadata").String()
|
||||
if gotMetadataPromptCacheKey := gjson.Get(gotBodyMetadata, "prompt_cache_key").String(); gotMetadataPromptCacheKey != expectedPromptCacheKey {
|
||||
t.Fatalf("client_metadata.x-codex-turn-metadata.prompt_cache_key = %q, want %q", gotMetadataPromptCacheKey, expectedPromptCacheKey)
|
||||
}
|
||||
if gotMetadataTurnID := gjson.Get(gotBodyMetadata, "turn_id").String(); gotMetadataTurnID != expectedTurnID {
|
||||
t.Fatalf("client_metadata.x-codex-turn-metadata.turn_id = %q, want %q", gotMetadataTurnID, expectedTurnID)
|
||||
}
|
||||
if gotMetadataWindowID := gjson.Get(gotBodyMetadata, "window_id").String(); gotMetadataWindowID != expectedPromptCacheKey+":0" {
|
||||
t.Fatalf("client_metadata.x-codex-turn-metadata.window_id = %q, want %q", gotMetadataWindowID, expectedPromptCacheKey+":0")
|
||||
}
|
||||
if gotWindowID := gjson.GetBytes(body, "client_metadata.x-codex-window-id").String(); gotWindowID != expectedPromptCacheKey+":0" {
|
||||
t.Fatalf("client_metadata.x-codex-window-id = %q, want %q", gotWindowID, expectedPromptCacheKey+":0")
|
||||
}
|
||||
if gotHeader := httpReq.Header["Session-Id"]; len(gotHeader) != 1 || gotHeader[0] != expectedPromptCacheKey {
|
||||
t.Fatalf("Session-Id = %#v, want [%q]", gotHeader, expectedPromptCacheKey)
|
||||
}
|
||||
for _, headerName := range []string{"X-Client-Request-Id", "Thread-Id"} {
|
||||
if gotHeader := httpReq.Header.Get(headerName); gotHeader != expectedPromptCacheKey {
|
||||
t.Fatalf("%s = %q, want %q", headerName, gotHeader, expectedPromptCacheKey)
|
||||
}
|
||||
}
|
||||
if gotLegacySession := httpReq.Header.Get("Session_id"); gotLegacySession != "" {
|
||||
t.Fatalf("Session_id = %q, want empty", gotLegacySession)
|
||||
}
|
||||
if gotWindow := httpReq.Header.Get("X-Codex-Window-Id"); gotWindow != expectedPromptCacheKey+":0" {
|
||||
t.Fatalf("X-Codex-Window-Id = %q, want %q", gotWindow, expectedPromptCacheKey+":0")
|
||||
}
|
||||
gotHeaderMetadata := httpReq.Header.Get("X-Codex-Turn-Metadata")
|
||||
if gotMetadataPromptCacheKey := gjson.Get(gotHeaderMetadata, "prompt_cache_key").String(); gotMetadataPromptCacheKey != expectedPromptCacheKey {
|
||||
t.Fatalf("X-Codex-Turn-Metadata.prompt_cache_key = %q, want %q", gotMetadataPromptCacheKey, expectedPromptCacheKey)
|
||||
}
|
||||
if gotMetadataTurnID := gjson.Get(gotHeaderMetadata, "turn_id").String(); gotMetadataTurnID != expectedTurnID {
|
||||
t.Fatalf("X-Codex-Turn-Metadata.turn_id = %q, want %q", gotMetadataTurnID, expectedTurnID)
|
||||
}
|
||||
if gotMetadataWindowID := gjson.Get(gotHeaderMetadata, "window_id").String(); gotMetadataWindowID != expectedPromptCacheKey+":0" {
|
||||
t.Fatalf("X-Codex-Turn-Metadata.window_id = %q, want %q", gotMetadataWindowID, expectedPromptCacheKey+":0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCodexHeadersUsesAccountHeaderForOAuth(t *testing.T) {
|
||||
httpReq := httptest.NewRequest("POST", "https://example.com/responses", nil)
|
||||
auth := &cliproxyauth.Auth{
|
||||
@@ -267,27 +191,6 @@ func TestApplyCodexHeadersUsesAccountHeaderForOAuth(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexIdentityConfuseKeepsClientBodySeparateFromUpstreamBody(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
Routing: config.RoutingConfig{Strategy: "fill-first"},
|
||||
Codex: config.CodexConfig{IdentityConfuse: true},
|
||||
}
|
||||
auth := &cliproxyauth.Auth{ID: "auth-1", Provider: "codex"}
|
||||
clientBody := []byte(`{"model":"gpt-5-codex","prompt_cache_key":"cache-1"}`)
|
||||
|
||||
upstreamBody, identityState := applyCodexIdentityConfuseBody(cfg, auth, clientBody, clientBody)
|
||||
expectedPromptCacheKey := codexIdentityConfuseUUID("auth-1", "prompt-cache", "cache-1")
|
||||
if identityState.promptCacheKey != expectedPromptCacheKey {
|
||||
t.Fatalf("identity prompt_cache_key = %q, want %q", identityState.promptCacheKey, expectedPromptCacheKey)
|
||||
}
|
||||
if gotKey := gjson.GetBytes(upstreamBody, "prompt_cache_key").String(); gotKey != expectedPromptCacheKey {
|
||||
t.Fatalf("upstream prompt_cache_key = %q, want %q", gotKey, expectedPromptCacheKey)
|
||||
}
|
||||
if gotKey := gjson.GetBytes(clientBody, "prompt_cache_key").String(); gotKey != "cache-1" {
|
||||
t.Fatalf("client prompt_cache_key = %q, want cache-1", gotKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCodexExecutorCacheHelper_ClaudeUsesSessionHeader(t *testing.T) {
|
||||
executor := &CodexExecutor{}
|
||||
recorder := httptest.NewRecorder()
|
||||
@@ -307,11 +210,11 @@ func TestCodexExecutorCacheHelper_ClaudeUsesSessionHeader(t *testing.T) {
|
||||
rawJSON := []byte(`{"model":"gpt-5.4","stream":true}`)
|
||||
url := "https://example.com/responses"
|
||||
|
||||
firstHTTPReq, _, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("claude"), url, nil, firstReq, firstReq.Payload, rawJSON)
|
||||
firstHTTPReq, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("claude"), url, firstReq, rawJSON)
|
||||
if err != nil {
|
||||
t.Fatalf("cacheHelper first error: %v", err)
|
||||
}
|
||||
secondHTTPReq, _, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("claude"), url, nil, secondReq, secondReq.Payload, rawJSON)
|
||||
secondHTTPReq, _, err := executor.cacheHelper(ctx, sdktranslator.FromString("claude"), url, secondReq, rawJSON)
|
||||
if err != nil {
|
||||
t.Fatalf("cacheHelper second error: %v", err)
|
||||
}
|
||||
@@ -347,7 +250,7 @@ func TestCodexExecutorCacheHelper_ClaudeAgentScopeUsesResolvedModelAcrossHTTPAnd
|
||||
childHeaders.Set(helps.ClaudeCodeAgentHeader, "agent-a")
|
||||
rawJSON := []byte(`{"model":"gpt-5.4","stream":true}`)
|
||||
|
||||
rootRequest, _, _, errRoot := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), url, nil, req, req.Payload, rawJSON, rootHeaders)
|
||||
rootRequest, _, errRoot := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), url, req, rawJSON, rootHeaders)
|
||||
if errRoot != nil {
|
||||
t.Fatalf("root cacheHelper error: %v", errRoot)
|
||||
}
|
||||
@@ -357,7 +260,7 @@ func TestCodexExecutorCacheHelper_ClaudeAgentScopeUsesResolvedModelAcrossHTTPAnd
|
||||
}
|
||||
rootKey := gjson.GetBytes(rootBody, "prompt_cache_key").String()
|
||||
|
||||
childRequest, _, _, errChild := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), url, nil, req, req.Payload, rawJSON, childHeaders)
|
||||
childRequest, _, errChild := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), url, req, rawJSON, childHeaders)
|
||||
if errChild != nil {
|
||||
t.Fatalf("child cacheHelper error: %v", errChild)
|
||||
}
|
||||
@@ -372,7 +275,7 @@ func TestCodexExecutorCacheHelper_ClaudeAgentScopeUsesResolvedModelAcrossHTTPAnd
|
||||
|
||||
aliasReq := req
|
||||
aliasReq.Model = "another-local-alias-low"
|
||||
aliasRequest, _, _, errAlias := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), url, nil, aliasReq, aliasReq.Payload, rawJSON, childHeaders)
|
||||
aliasRequest, _, errAlias := executor.cacheHelper(context.Background(), sdktranslator.FromString("claude"), url, aliasReq, rawJSON, childHeaders)
|
||||
if errAlias != nil {
|
||||
t.Fatalf("alias cacheHelper error: %v", errAlias)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user