Skip to content

Commit fa21523

Browse files
committed
release: bump version to 2.2.9 and update various configurations
- 更新版本号从2.2.8到2.2.9 - 修改免费账号默认状态为关闭 - 为API请求结构体新增ArtifactDelivery字段 - 新增token禁用机制和相关处理逻辑 - 优化ChatGPT请求参数的omitempty标签 - 新增大量单元测试覆盖请求响应处理 - 重构流式响应处理逻辑,支持websocket切换 - 更新文档说明和模型列表 - 删除过时的文件问答测试文件
1 parent 1abc2d5 commit fa21523

17 files changed

Lines changed: 1481 additions & 243 deletions

File tree

.github/workflows/build.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ name: Build and Release
22

33
on:
44
workflow_dispatch:
5+
push:
6+
paths:
7+
- 'VERSION'
58

69
permissions:
710
contents: write

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ tools/authenticator/.proxies.txt.swp
1313
/logs/
1414
/target/
1515
/bin/
16+
.claude
1617
.gocache

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ PROXY_URL=your_proxy_url
7070

7171
- `SERVER_HOST` / `SERVER_PORT`:服务监听地址和端口。
7272
- `Authorization`:服务访问 key。配置后,请求头需携带 `Authorization: Bearer your_authorization`
73-
- `FREE_ACCOUNTS`:是否自动生成免费 UUID 账号,默认开启
73+
- `FREE_ACCOUNTS`:是否自动生成免费 UUID 账号,默认关闭
7474
- `FREE_ACCOUNTS_NUM`:自动生成免费 UUID 账号数量,默认 1024。
7575
- `TLS_CERT` / `TLS_KEY`:同时配置时启用 HTTPS。
7676
- `PROXY_URL`:代理池地址。

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.2.8
1+
2.2.9

httpclient/bogdanfinn/tls_client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ func NewStdClient() *TlsClient {
2222
client, _ := tls_client.NewHttpClient(tls_client.NewNoopLogger(), []tls_client.HttpClientOption{
2323
tls_client.WithCookieJar(tls_client.NewCookieJar()),
2424
tls_client.WithTimeoutSeconds(600),
25-
tls_client.WithClientProfile(profiles.Safari_IOS_26_0),
25+
tls_client.WithClientProfile(profiles.Chrome_146),
2626
}...)
2727

2828
stdClient := &TlsClient{Client: client}

initialize/auth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ func readAccessToken() *tokens.AccessToken {
5656
}
5757
}
5858

59-
if os.Getenv("FREE_ACCOUNTS") == "" || os.Getenv("FREE_ACCOUNTS") == "true" {
59+
if os.Getenv("FREE_ACCOUNTS") == "true" {
6060
freeAccountsNumStr := os.Getenv("FREE_ACCOUNTS_NUM")
6161
numAccounts := 1024
6262
if freeAccountsNumStr != "" {

initialize/handlers.go

Lines changed: 86 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,44 @@ type Handler struct {
2626
token *tokens.AccessToken
2727
}
2828

29+
func writeChatCompletionStreamDone(c *gin.Context, stopSent bool, model string, conversationID string) {
30+
if !stopSent {
31+
finalLine := officialtypes.StopChunkWithConversation("stop", model, conversationID)
32+
c.Writer.WriteString("data: " + finalLine.String() + "\n\n")
33+
c.Writer.Flush()
34+
}
35+
c.Writer.WriteString("data: [DONE]\n\n")
36+
c.Writer.Flush()
37+
}
38+
2939
func NewHandle(proxy *proxys.IProxy, token *tokens.AccessToken) *Handler {
3040
return &Handler{proxy: proxy, token: token}
3141
}
3242

43+
// initTurnStileWithRetry 初始化 turnstile,当 paid token 返回 401 时自动禁用并轮询下一个
44+
func (h *Handler) initTurnStileWithRetry(client **bogdanfinn.TlsClient, secret **tokens.Secret, proxyUrl string) (*chatgpt.TurnStile, int, error) {
45+
for {
46+
(*client).SetCookies("https://chatgpt.com", chatgpt.BasicCookies)
47+
turnStile, status, err := chatgpt.InitTurnStile(*client, *secret, proxyUrl)
48+
if err == nil {
49+
return turnStile, status, nil
50+
}
51+
if status == http.StatusUnauthorized && *secret != nil && !(*secret).IsFree && (*secret).Token != "" {
52+
if !h.token.DisableSecret((*secret).Token) {
53+
return nil, status, err
54+
}
55+
newSecret := h.token.GetPaidSecret()
56+
if newSecret == nil || newSecret.Token == "" {
57+
return nil, status, err
58+
}
59+
*secret = newSecret
60+
*client = bogdanfinn.NewStdClient()
61+
continue
62+
}
63+
return nil, status, err
64+
}
65+
}
66+
3367
func (h *Handler) refresh(c *gin.Context) {
3468
var refreshToken officialtypes.OpenAIRefreshToken
3569
err := c.BindJSON(&refreshToken)
@@ -202,8 +236,7 @@ func (h *Handler) nightmare(c *gin.Context) {
202236

203237
uid := uuid.NewString()
204238
client := bogdanfinn.NewStdClient()
205-
client.SetCookies("https://chatgpt.com", chatgpt.BasicCookies)
206-
turnStile, status, err := chatgpt.InitTurnStile(client, secret, proxyUrl)
239+
turnStile, status, err := h.initTurnStileWithRetry(&client, &secret, proxyUrl)
207240
if err != nil {
208241
c.JSON(status, gin.H{
209242
"message": err.Error(),
@@ -225,25 +258,44 @@ func (h *Handler) nightmare(c *gin.Context) {
225258

226259
response, err := chatgpt.POSTconversation(client, translated_request, secret, turnStile, proxyUrl)
227260
if err != nil {
228-
c.JSON(500, gin.H{
229-
"error": "request conversion error",
230-
})
261+
c.JSON(500, gin.H{"error": gin.H{
262+
"message": err.Error(),
263+
"type": "request_conversion_error",
264+
"param": "model",
265+
"code": "request_conversion_error",
266+
}})
231267
return
232268
}
233269
defer response.Body.Close()
234270
if chatgpt.Handle_request_error(c, response) {
235271
return
236272
}
237273
var full_response string
274+
var conversationID string
275+
var sentinel []map[string]interface{}
276+
var stopSent bool
238277

239278
if os.Getenv("STREAM_MODE") == "false" {
240279
original_request.Stream = false
241280
}
281+
if original_request.Stream {
282+
c.Writer.Header().Set("Content-Type", "text/event-stream")
283+
c.Writer.Header().Set("Cache-Control", "no-cache")
284+
c.Writer.Header().Set("Connection", "keep-alive")
285+
c.Writer.Header().Set("X-Accel-Buffering", "no")
286+
}
242287
for i := 3; i > 0; i-- {
243288
var continue_info *chatgpt.ContinueInfo
244-
var response_part string
245-
response_part, continue_info = chatgpt.Handler(c, response, client, secret, uid, translated_request, original_request.Stream, reqModel)
246-
full_response += response_part
289+
result := chatgpt.HandlerDetailed(c, response, client, secret, uid, translated_request, original_request.Stream, reqModel)
290+
continue_info = result.Continue
291+
full_response += result.Text
292+
if result.ConversationID != "" {
293+
conversationID = result.ConversationID
294+
}
295+
sentinel = append(sentinel, result.Sentinel...)
296+
if result.StopSent {
297+
stopSent = true
298+
}
247299
if continue_info == nil {
248300
break
249301
}
@@ -254,9 +306,12 @@ func (h *Handler) nightmare(c *gin.Context) {
254306

255307
response, err := chatgpt.POSTconversation(client, translated_request, secret, turnStile, proxyUrl)
256308
if err != nil {
257-
c.JSON(500, gin.H{
258-
"error": "request conversion error",
259-
})
309+
c.JSON(500, gin.H{"error": gin.H{
310+
"message": err.Error(),
311+
"type": "request_conversion_error",
312+
"param": "model",
313+
"code": "request_conversion_error",
314+
}})
260315
return
261316
}
262317
defer response.Body.Close()
@@ -269,9 +324,9 @@ func (h *Handler) nightmare(c *gin.Context) {
269324
}
270325
if !original_request.Stream {
271326
output_tokens := util.CountToken(full_response)
272-
c.JSON(200, officialtypes.NewChatCompletion(full_response, input_tokens, output_tokens, reqModel))
327+
c.JSON(200, officialtypes.NewChatCompletionWithMetadata(full_response, input_tokens, output_tokens, reqModel, conversationID, sentinel))
273328
} else {
274-
c.String(200, "data: [DONE]\n\n")
329+
writeChatCompletionStreamDone(c, stopSent, reqModel, conversationID)
275330
}
276331
}
277332

@@ -322,8 +377,7 @@ func (h *Handler) responses(c *gin.Context) {
322377

323378
uid := uuid.NewString()
324379
client := bogdanfinn.NewStdClient()
325-
client.SetCookies("https://chatgpt.com", chatgpt.BasicCookies)
326-
turnStile, status, err := chatgpt.InitTurnStile(client, secret, proxyUrl)
380+
turnStile, status, err := h.initTurnStileWithRetry(&client, &secret, proxyUrl)
327381
if err != nil {
328382
c.JSON(status, gin.H{
329383
"message": err.Error(),
@@ -342,7 +396,12 @@ func (h *Handler) responses(c *gin.Context) {
342396

343397
response, err := chatgpt.POSTconversation(client, translated_request, secret, turnStile, proxyUrl)
344398
if err != nil {
345-
c.JSON(500, gin.H{"error": "request conversion error"})
399+
c.JSON(500, gin.H{"error": gin.H{
400+
"message": err.Error(),
401+
"type": "request_conversion_error",
402+
"param": "model",
403+
"code": "request_conversion_error",
404+
}})
346405
return
347406
}
348407
defer response.Body.Close()
@@ -366,7 +425,12 @@ func (h *Handler) responses(c *gin.Context) {
366425

367426
response, err = chatgpt.POSTconversation(client, translated_request, secret, turnStile, proxyUrl)
368427
if err != nil {
369-
c.JSON(500, gin.H{"error": "request conversion error"})
428+
c.JSON(500, gin.H{"error": gin.H{
429+
"message": err.Error(),
430+
"type": "request_conversion_error",
431+
"param": "model",
432+
"code": "request_conversion_error",
433+
}})
370434
return
371435
}
372436
defer response.Body.Close()
@@ -447,8 +511,7 @@ func (h *Handler) imageGenerations(c *gin.Context) {
447511
}
448512

449513
client := bogdanfinn.NewStdClient()
450-
client.SetCookies("https://chatgpt.com", chatgpt.BasicCookies)
451-
turnStile, status, err := chatgpt.InitTurnStile(client, secret, proxyUrl)
514+
turnStile, status, err := h.initTurnStileWithRetry(&client, &secret, proxyUrl)
452515
if err != nil {
453516
c.JSON(status, gin.H{
454517
"message": err.Error(),
@@ -622,9 +685,9 @@ func (h *Handler) engines(c *gin.Context) {
622685

623686
models := []string{
624687
"auto",
625-
"gpt-5.5-instant",
688+
"gpt-5-5-instant",
626689
"gpt-5-5-thinking",
627-
"gpt-5.5-pro",
690+
"gpt-5-5-pro",
628691
"gpt-5",
629692
"gpt-4o",
630693
"gpt-4o-mini",
@@ -817,8 +880,7 @@ func (h *Handler) tts(c *gin.Context) {
817880
}
818881

819882
client := bogdanfinn.NewStdClient()
820-
client.SetCookies("https://chatgpt.com", chatgpt.BasicCookies)
821-
turnStile, status, err := chatgpt.InitTurnStile(client, secret, proxyUrl)
883+
turnStile, status, err := h.initTurnStileWithRetry(&client, &secret, proxyUrl)
822884
if err != nil {
823885
c.JSON(status, gin.H{
824886
"message": err.Error(),
@@ -901,7 +963,7 @@ func (h *Handler) chatgptConversation(c *gin.Context) {
901963
}
902964

903965
client := bogdanfinn.NewStdClient()
904-
turnStile, status, err := chatgpt.InitTurnStile(client, secret, proxyUrl)
966+
turnStile, status, err := h.initTurnStileWithRetry(&client, &secret, proxyUrl)
905967
if err != nil {
906968
c.JSON(status, gin.H{
907969
"message": err.Error(),

initialize/handlers_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package initialize
22

33
import (
4+
"encoding/json"
45
"net/http"
56
"net/http/httptest"
7+
"strings"
68
"testing"
79

810
"github.com/gin-gonic/gin"
@@ -85,6 +87,46 @@ func TestAuthorizationTokenAndTeam(t *testing.T) {
8587
}
8688
}
8789

90+
func TestWriteChatCompletionStreamDoneAddsStopBeforeDone(t *testing.T) {
91+
gin.SetMode(gin.TestMode)
92+
writer := httptest.NewRecorder()
93+
c, _ := gin.CreateTestContext(writer)
94+
95+
writeChatCompletionStreamDone(c, false, "auto", "conv-xxx")
96+
97+
lines := sseDataLines(writer.Body.String())
98+
if len(lines) != 2 {
99+
t.Fatalf("data line count = %d, want 2; output: %s", len(lines), writer.Body.String())
100+
}
101+
var stopChunk map[string]interface{}
102+
if err := json.Unmarshal([]byte(lines[0]), &stopChunk); err != nil {
103+
t.Fatalf("invalid stop chunk: %v", err)
104+
}
105+
if stopChunk["conversation_id"] != "conv-xxx" {
106+
t.Fatalf("conversation_id = %#v, want conv-xxx", stopChunk["conversation_id"])
107+
}
108+
choices := stopChunk["choices"].([]interface{})
109+
if choices[0].(map[string]interface{})["finish_reason"] != "stop" {
110+
t.Fatalf("finish_reason = %#v, want stop", choices[0].(map[string]interface{})["finish_reason"])
111+
}
112+
if lines[1] != "[DONE]" {
113+
t.Fatalf("last data line = %q, want [DONE]", lines[1])
114+
}
115+
}
116+
117+
func TestWriteChatCompletionStreamDoneSkipsDuplicateStop(t *testing.T) {
118+
gin.SetMode(gin.TestMode)
119+
writer := httptest.NewRecorder()
120+
c, _ := gin.CreateTestContext(writer)
121+
122+
writeChatCompletionStreamDone(c, true, "auto", "conv-xxx")
123+
124+
lines := sseDataLines(writer.Body.String())
125+
if len(lines) != 1 || lines[0] != "[DONE]" {
126+
t.Fatalf("data lines = %#v, want only [DONE]", lines)
127+
}
128+
}
129+
88130
func testContextWithHeaders(headers map[string]string) *gin.Context {
89131
w := httptest.NewRecorder()
90132
c, _ := gin.CreateTestContext(w)
@@ -95,3 +137,15 @@ func testContextWithHeaders(headers map[string]string) *gin.Context {
95137
c.Request = req
96138
return c
97139
}
140+
141+
func sseDataLines(output string) []string {
142+
var lines []string
143+
for _, line := range strings.Split(output, "\n") {
144+
line = strings.TrimSpace(line)
145+
if !strings.HasPrefix(line, "data: ") {
146+
continue
147+
}
148+
lines = append(lines, strings.TrimPrefix(line, "data: "))
149+
}
150+
return lines
151+
}

0 commit comments

Comments
 (0)