-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
fix: 修复 Codex WebSocket 会话在额度耗尽后不切换账号 #2256
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
cc5cb31
32e8d1d
b9b9f77
ae8557e
42acfa7
e9a98a3
51ea8f1
7e7cc1a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -192,13 +192,21 @@ func (h *OpenAIResponsesAPIHandler) ResponsesWebsocket(c *gin.Context) { | |
| } | ||
| dataChan, _, errChan := h.ExecuteStreamWithAuthManager(cliCtx, h.HandlerType(), modelName, requestJSON, "") | ||
|
|
||
| completedOutput, errForward := h.forwardResponsesWebsocket(c, conn, cliCancel, dataChan, errChan, &wsBodyLog, passthroughSessionID) | ||
| completedOutput, terminalStatus, errForward := h.forwardResponsesWebsocket(c, conn, cliCancel, dataChan, errChan, &wsBodyLog, passthroughSessionID) | ||
| if errForward != nil { | ||
| wsTerminateErr = errForward | ||
| appendWebsocketEvent(&wsBodyLog, "disconnect", []byte(errForward.Error())) | ||
| log.Warnf("responses websocket: forward failed id=%s error=%v", passthroughSessionID, errForward) | ||
| return | ||
| } | ||
| if shouldResetResponsesWebsocketAuthPin(terminalStatus) { | ||
| // 限额错误后解除 pin 让后续请求重新选可用账号 | ||
| pinnedAuthID = "" | ||
| if h != nil && h.AuthManager != nil { | ||
|
Comment on lines
+225
to
+231
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
After a quota-class terminal error this block clears Useful? React with 👍 / 👎. |
||
| // 主动关闭旧上游会话避免继续复用旧账号连接 | ||
| h.AuthManager.CloseExecutionSession(passthroughSessionID) | ||
| } | ||
| } | ||
| lastResponseOutput = completedOutput | ||
| } | ||
| } | ||
|
|
@@ -610,21 +618,23 @@ func (h *OpenAIResponsesAPIHandler) forwardResponsesWebsocket( | |
| errs <-chan *interfaces.ErrorMessage, | ||
| wsBodyLog *strings.Builder, | ||
| sessionID string, | ||
| ) ([]byte, error) { | ||
| ) ([]byte, int, error) { | ||
| completed := false | ||
| completedOutput := []byte("[]") | ||
| terminalStatusCode := 0 | ||
|
|
||
| for { | ||
| select { | ||
| case <-c.Request.Context().Done(): | ||
| cancel(c.Request.Context().Err()) | ||
| return completedOutput, c.Request.Context().Err() | ||
| return completedOutput, terminalStatusCode, c.Request.Context().Err() | ||
| case errMsg, ok := <-errs: | ||
| if !ok { | ||
| errs = nil | ||
| continue | ||
| } | ||
| if errMsg != nil { | ||
| terminalStatusCode = errMsg.StatusCode | ||
| h.LoggingAPIResponseError(context.WithValue(context.Background(), "gin", c), errMsg) | ||
| markAPIResponseTimestamp(c) | ||
| errorPayload, errWrite := writeResponsesWebsocketError(conn, errMsg) | ||
|
|
@@ -644,22 +654,23 @@ func (h *OpenAIResponsesAPIHandler) forwardResponsesWebsocket( | |
| // errWrite, | ||
| // ) | ||
| cancel(errMsg.Error) | ||
| return completedOutput, errWrite | ||
| return completedOutput, terminalStatusCode, errWrite | ||
| } | ||
| } | ||
| if errMsg != nil { | ||
| cancel(errMsg.Error) | ||
| } else { | ||
| cancel(nil) | ||
| } | ||
| return completedOutput, nil | ||
| return completedOutput, terminalStatusCode, nil | ||
| case chunk, ok := <-data: | ||
| if !ok { | ||
| if !completed { | ||
| errMsg := &interfaces.ErrorMessage{ | ||
| StatusCode: http.StatusRequestTimeout, | ||
| Error: fmt.Errorf("stream closed before response.completed"), | ||
| } | ||
| terminalStatusCode = errMsg.StatusCode | ||
| h.LoggingAPIResponseError(context.WithValue(context.Background(), "gin", c), errMsg) | ||
| markAPIResponseTimestamp(c) | ||
| errorPayload, errWrite := writeResponsesWebsocketError(conn, errMsg) | ||
|
|
@@ -679,13 +690,13 @@ func (h *OpenAIResponsesAPIHandler) forwardResponsesWebsocket( | |
| errWrite, | ||
| ) | ||
| cancel(errMsg.Error) | ||
| return completedOutput, errWrite | ||
| return completedOutput, terminalStatusCode, errWrite | ||
| } | ||
| cancel(errMsg.Error) | ||
| return completedOutput, nil | ||
| return completedOutput, terminalStatusCode, nil | ||
| } | ||
| cancel(nil) | ||
| return completedOutput, nil | ||
| return completedOutput, terminalStatusCode, nil | ||
| } | ||
|
|
||
| payloads := websocketJSONPayloadsFromChunk(chunk) | ||
|
|
@@ -712,13 +723,22 @@ func (h *OpenAIResponsesAPIHandler) forwardResponsesWebsocket( | |
| errWrite, | ||
| ) | ||
| cancel(errWrite) | ||
| return completedOutput, errWrite | ||
| return completedOutput, terminalStatusCode, errWrite | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| func shouldResetResponsesWebsocketAuthPin(statusCode int) bool { | ||
| switch statusCode { | ||
| case http.StatusTooManyRequests, http.StatusForbidden, http.StatusPaymentRequired: | ||
| return true | ||
| default: | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| func responseCompletedOutputFromPayload(payload []byte) []byte { | ||
| output := gjson.GetBytes(payload, "response.output") | ||
| if output.Exists() && output.IsArray() { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling
invalidateUpstreamConnduring auth changes closes the old socket immediately, but the oldreadUpstreamLoopcan still wake later and run its error path, which unconditionallyclearActive/closes whatever channel is currently active for the session. If a new request has already installed its ownactiveCh, that channel gets closed andreadCodexWebsocketMessagereturnssession read channel closed, aborting a healthy post-switch request. This race is specific to in-session auth switching and can break the new quota-recovery flow intermittently.Useful? React with 👍 / 👎.