将 vansour/sub2api#1555 的 OpenAI compact 能力建模手工移植到当前 main:账号 级 compact 状态/auto-force_on-force_off 模式、compact-only 模型映射、调度器 tier 分层(已支持 > 未知 > 已知不支持)、管理后台 compact 主动探测,以及对应 i18n/状态徽章。普通 /responses 流量行为不变,无数据库迁移。
136 lines
5.2 KiB
Go
136 lines
5.2 KiB
Go
package service
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/tidwall/gjson"
|
|
)
|
|
|
|
func TestOpenAIGatewayService_Forward_CompactOnlyModelMappingOverridesOAuthUpstreamModel(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
rec := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(rec)
|
|
body := []byte(`{"model":"gpt-5.4","stream":false,"instructions":"compact-test","input":"hello"}`)
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses/compact", bytes.NewReader(body))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
upstream := &httpUpstreamRecorder{resp: &http.Response{
|
|
StatusCode: http.StatusOK,
|
|
Header: http.Header{"Content-Type": []string{"application/json"}, "x-request-id": []string{"rid-compact-map"}},
|
|
Body: io.NopCloser(strings.NewReader(`{"id":"resp_123","status":"completed","model":"gpt-5.4-openai-compact","output":[],"usage":{"input_tokens":1,"output_tokens":1}}`)),
|
|
}}
|
|
|
|
svc := &OpenAIGatewayService{httpUpstream: upstream}
|
|
account := &Account{
|
|
ID: 1,
|
|
Name: "openai-oauth",
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeOAuth,
|
|
Concurrency: 1,
|
|
Credentials: map[string]any{
|
|
"access_token": "oauth-token",
|
|
"chatgpt_account_id": "chatgpt-acc",
|
|
"compact_model_mapping": map[string]any{"gpt-5.4": "gpt-5.4-openai-compact"},
|
|
},
|
|
Status: StatusActive,
|
|
Schedulable: true,
|
|
}
|
|
|
|
result, err := svc.Forward(context.Background(), c, account, body)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
require.Equal(t, "gpt-5.4", result.Model)
|
|
require.Equal(t, "gpt-5.4-openai-compact", result.UpstreamModel)
|
|
require.Equal(t, "gpt-5.4-openai-compact", gjson.GetBytes(upstream.lastBody, "model").String())
|
|
}
|
|
|
|
func TestOpenAIGatewayService_Forward_NonCompactRequestIgnoresCompactOnlyModelMapping(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
rec := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(rec)
|
|
body := []byte(`{"model":"gpt-5.4","stream":false,"instructions":"normal-test","input":"hello"}`)
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses", bytes.NewReader(body))
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
upstream := &httpUpstreamRecorder{resp: &http.Response{
|
|
StatusCode: http.StatusOK,
|
|
Header: http.Header{"Content-Type": []string{"application/json"}, "x-request-id": []string{"rid-normal-map"}},
|
|
Body: io.NopCloser(strings.NewReader(`{"id":"resp_124","status":"completed","model":"gpt-5.4","output":[],"usage":{"input_tokens":1,"output_tokens":1}}`)),
|
|
}}
|
|
|
|
svc := &OpenAIGatewayService{httpUpstream: upstream}
|
|
account := &Account{
|
|
ID: 2,
|
|
Name: "openai-oauth",
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeOAuth,
|
|
Concurrency: 1,
|
|
Credentials: map[string]any{
|
|
"access_token": "oauth-token",
|
|
"chatgpt_account_id": "chatgpt-acc",
|
|
"compact_model_mapping": map[string]any{"gpt-5.4": "gpt-5.4-openai-compact"},
|
|
},
|
|
Status: StatusActive,
|
|
Schedulable: true,
|
|
}
|
|
|
|
result, err := svc.Forward(context.Background(), c, account, body)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
require.Equal(t, "gpt-5.4", result.Model)
|
|
require.Equal(t, "gpt-5.4", result.UpstreamModel)
|
|
require.Equal(t, "gpt-5.4", gjson.GetBytes(upstream.lastBody, "model").String())
|
|
}
|
|
|
|
func TestOpenAIGatewayService_OAuthPassthrough_CompactOnlyModelMappingOverridesUpstreamModel(t *testing.T) {
|
|
gin.SetMode(gin.TestMode)
|
|
|
|
rec := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(rec)
|
|
c.Request = httptest.NewRequest(http.MethodPost, "/v1/responses/compact", bytes.NewReader(nil))
|
|
c.Request.Header.Set("User-Agent", "codex_cli_rs/0.1.0")
|
|
c.Request.Header.Set("Content-Type", "application/json")
|
|
|
|
originalBody := []byte(`{"model":"gpt-5.4","stream":true,"store":true,"instructions":"compact-pass","input":[{"type":"text","text":"compact me"}]}`)
|
|
upstream := &httpUpstreamRecorder{resp: &http.Response{
|
|
StatusCode: http.StatusOK,
|
|
Header: http.Header{"Content-Type": []string{"application/json"}, "x-request-id": []string{"rid-compact-pass-map"}},
|
|
Body: io.NopCloser(strings.NewReader(`{"id":"cmp_124","model":"gpt-5.4-openai-compact","usage":{"input_tokens":2,"output_tokens":3}}`)),
|
|
}}
|
|
|
|
svc := &OpenAIGatewayService{httpUpstream: upstream}
|
|
account := &Account{
|
|
ID: 3,
|
|
Name: "openai-oauth-pass",
|
|
Platform: PlatformOpenAI,
|
|
Type: AccountTypeOAuth,
|
|
Concurrency: 1,
|
|
Credentials: map[string]any{
|
|
"access_token": "oauth-token",
|
|
"chatgpt_account_id": "chatgpt-acc",
|
|
"compact_model_mapping": map[string]any{"gpt-5.4": "gpt-5.4-openai-compact"},
|
|
},
|
|
Extra: map[string]any{"openai_passthrough": true},
|
|
Status: StatusActive,
|
|
Schedulable: true,
|
|
}
|
|
|
|
result, err := svc.Forward(context.Background(), c, account, originalBody)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result)
|
|
require.Equal(t, "gpt-5.4", result.Model)
|
|
require.Equal(t, "gpt-5.4-openai-compact", result.UpstreamModel)
|
|
require.Equal(t, "gpt-5.4-openai-compact", gjson.GetBytes(upstream.lastBody, "model").String())
|
|
require.Equal(t, "gpt-5.4", gjson.GetBytes(rec.Body.Bytes(), "model").String())
|
|
}
|