@@ -31,7 +31,7 @@ import (
3131)
3232
3333const (
34- codexResponsesWebsocketBetaHeaderValue = "responses_websockets=2026-02-04 "
34+ codexResponsesWebsocketBetaHeaderValue = "responses_websockets=2026-02-06 "
3535 codexResponsesWebsocketIdleTimeout = 5 * time .Minute
3636 codexResponsesWebsocketHandshakeTO = 30 * time .Second
3737)
@@ -57,11 +57,6 @@ type codexWebsocketSession struct {
5757 wsURL string
5858 authID string
5959
60- // connCreateSent tracks whether a `response.create` message has been successfully sent
61- // on the current websocket connection. The upstream expects the first message on each
62- // connection to be `response.create`.
63- connCreateSent bool
64-
6560 writeMu sync.Mutex
6661
6762 activeMu sync.Mutex
@@ -212,13 +207,7 @@ func (e *CodexWebsocketsExecutor) Execute(ctx context.Context, auth *cliproxyaut
212207 defer sess .reqMu .Unlock ()
213208 }
214209
215- allowAppend := true
216- if sess != nil {
217- sess .connMu .Lock ()
218- allowAppend = sess .connCreateSent
219- sess .connMu .Unlock ()
220- }
221- wsReqBody := buildCodexWebsocketRequestBody (body , allowAppend )
210+ wsReqBody := buildCodexWebsocketRequestBody (body )
222211 recordAPIRequest (ctx , e .cfg , upstreamRequestLog {
223212 URL : wsURL ,
224213 Method : "WEBSOCKET" ,
@@ -280,10 +269,7 @@ func (e *CodexWebsocketsExecutor) Execute(ctx context.Context, auth *cliproxyaut
280269 // execution session.
281270 connRetry , _ , errDialRetry := e .ensureUpstreamConn (ctx , auth , sess , authID , wsURL , wsHeaders )
282271 if errDialRetry == nil && connRetry != nil {
283- sess .connMu .Lock ()
284- allowAppend = sess .connCreateSent
285- sess .connMu .Unlock ()
286- wsReqBodyRetry := buildCodexWebsocketRequestBody (body , allowAppend )
272+ wsReqBodyRetry := buildCodexWebsocketRequestBody (body )
287273 recordAPIRequest (ctx , e .cfg , upstreamRequestLog {
288274 URL : wsURL ,
289275 Method : "WEBSOCKET" ,
@@ -312,7 +298,6 @@ func (e *CodexWebsocketsExecutor) Execute(ctx context.Context, auth *cliproxyaut
312298 return resp , errSend
313299 }
314300 }
315- markCodexWebsocketCreateSent (sess , conn , wsReqBody )
316301
317302 for {
318303 if ctx != nil && ctx .Err () != nil {
@@ -403,26 +388,20 @@ func (e *CodexWebsocketsExecutor) ExecuteStream(ctx context.Context, auth *clipr
403388 wsHeaders = applyCodexWebsocketHeaders (ctx , wsHeaders , auth , apiKey )
404389
405390 var authID , authLabel , authType , authValue string
406- if auth != nil {
407- authID = auth .ID
408- authLabel = auth .Label
409- authType , authValue = auth .AccountInfo ()
410- }
391+ authID = auth .ID
392+ authLabel = auth .Label
393+ authType , authValue = auth .AccountInfo ()
411394
412395 executionSessionID := executionSessionIDFromOptions (opts )
413396 var sess * codexWebsocketSession
414397 if executionSessionID != "" {
415398 sess = e .getOrCreateSession (executionSessionID )
416- sess .reqMu .Lock ()
399+ if sess != nil {
400+ sess .reqMu .Lock ()
401+ }
417402 }
418403
419- allowAppend := true
420- if sess != nil {
421- sess .connMu .Lock ()
422- allowAppend = sess .connCreateSent
423- sess .connMu .Unlock ()
424- }
425- wsReqBody := buildCodexWebsocketRequestBody (body , allowAppend )
404+ wsReqBody := buildCodexWebsocketRequestBody (body )
426405 recordAPIRequest (ctx , e .cfg , upstreamRequestLog {
427406 URL : wsURL ,
428407 Method : "WEBSOCKET" ,
@@ -483,10 +462,7 @@ func (e *CodexWebsocketsExecutor) ExecuteStream(ctx context.Context, auth *clipr
483462 sess .reqMu .Unlock ()
484463 return nil , errDialRetry
485464 }
486- sess .connMu .Lock ()
487- allowAppend = sess .connCreateSent
488- sess .connMu .Unlock ()
489- wsReqBodyRetry := buildCodexWebsocketRequestBody (body , allowAppend )
465+ wsReqBodyRetry := buildCodexWebsocketRequestBody (body )
490466 recordAPIRequest (ctx , e .cfg , upstreamRequestLog {
491467 URL : wsURL ,
492468 Method : "WEBSOCKET" ,
@@ -515,7 +491,6 @@ func (e *CodexWebsocketsExecutor) ExecuteStream(ctx context.Context, auth *clipr
515491 return nil , errSend
516492 }
517493 }
518- markCodexWebsocketCreateSent (sess , conn , wsReqBody )
519494
520495 out := make (chan cliproxyexecutor.StreamChunk )
521496 go func () {
@@ -657,31 +632,14 @@ func writeCodexWebsocketMessage(sess *codexWebsocketSession, conn *websocket.Con
657632 return conn .WriteMessage (websocket .TextMessage , payload )
658633}
659634
660- func buildCodexWebsocketRequestBody (body []byte , allowAppend bool ) []byte {
635+ func buildCodexWebsocketRequestBody (body []byte ) []byte {
661636 if len (body ) == 0 {
662637 return nil
663638 }
664639
665- // Codex CLI websocket v2 uses `response.create` with `previous_response_id` for incremental turns.
666- // The upstream ChatGPT Codex websocket currently rejects that with close 1008 (policy violation).
667- // Fall back to v1 `response.append` semantics on the same websocket connection to keep the session alive.
668- //
669- // NOTE: The upstream expects the first websocket event on each connection to be `response.create`,
670- // so we only use `response.append` after we have initialized the current connection.
671- if allowAppend {
672- if prev := strings .TrimSpace (gjson .GetBytes (body , "previous_response_id" ).String ()); prev != "" {
673- inputNode := gjson .GetBytes (body , "input" )
674- wsReqBody := []byte (`{}` )
675- wsReqBody , _ = sjson .SetBytes (wsReqBody , "type" , "response.append" )
676- if inputNode .Exists () && inputNode .IsArray () && strings .TrimSpace (inputNode .Raw ) != "" {
677- wsReqBody , _ = sjson .SetRawBytes (wsReqBody , "input" , []byte (inputNode .Raw ))
678- return wsReqBody
679- }
680- wsReqBody , _ = sjson .SetRawBytes (wsReqBody , "input" , []byte ("[]" ))
681- return wsReqBody
682- }
683- }
684-
640+ // Match codex-rs websocket v2 semantics: every request is `response.create`.
641+ // Incremental follow-up turns continue on the same websocket using
642+ // `previous_response_id` + incremental `input`, not `response.append`.
685643 wsReqBody , errSet := sjson .SetBytes (bytes .Clone (body ), "type" , "response.create" )
686644 if errSet == nil && len (wsReqBody ) > 0 {
687645 return wsReqBody
@@ -725,21 +683,6 @@ func readCodexWebsocketMessage(ctx context.Context, sess *codexWebsocketSession,
725683 }
726684}
727685
728- func markCodexWebsocketCreateSent (sess * codexWebsocketSession , conn * websocket.Conn , payload []byte ) {
729- if sess == nil || conn == nil || len (payload ) == 0 {
730- return
731- }
732- if strings .TrimSpace (gjson .GetBytes (payload , "type" ).String ()) != "response.create" {
733- return
734- }
735-
736- sess .connMu .Lock ()
737- if sess .conn == conn {
738- sess .connCreateSent = true
739- }
740- sess .connMu .Unlock ()
741- }
742-
743686func newProxyAwareWebsocketDialer (cfg * config.Config , auth * cliproxyauth.Auth ) * websocket.Dialer {
744687 dialer := & websocket.Dialer {
745688 Proxy : http .ProxyFromEnvironment ,
@@ -1017,36 +960,6 @@ func closeHTTPResponseBody(resp *http.Response, logPrefix string) {
1017960 }
1018961}
1019962
1020- func closeOnContextDone (ctx context.Context , conn * websocket.Conn ) chan struct {} {
1021- done := make (chan struct {})
1022- if ctx == nil || conn == nil {
1023- return done
1024- }
1025- go func () {
1026- select {
1027- case <- done :
1028- case <- ctx .Done ():
1029- _ = conn .Close ()
1030- }
1031- }()
1032- return done
1033- }
1034-
1035- func cancelReadOnContextDone (ctx context.Context , conn * websocket.Conn ) chan struct {} {
1036- done := make (chan struct {})
1037- if ctx == nil || conn == nil {
1038- return done
1039- }
1040- go func () {
1041- select {
1042- case <- done :
1043- case <- ctx .Done ():
1044- _ = conn .SetReadDeadline (time .Now ())
1045- }
1046- }()
1047- return done
1048- }
1049-
1050963func executionSessionIDFromOptions (opts cliproxyexecutor.Options ) string {
1051964 if len (opts .Metadata ) == 0 {
1052965 return ""
@@ -1120,7 +1033,6 @@ func (e *CodexWebsocketsExecutor) ensureUpstreamConn(ctx context.Context, auth *
11201033 sess .conn = conn
11211034 sess .wsURL = wsURL
11221035 sess .authID = authID
1123- sess .connCreateSent = false
11241036 sess .readerConn = conn
11251037 sess .connMu .Unlock ()
11261038
@@ -1206,7 +1118,6 @@ func (e *CodexWebsocketsExecutor) invalidateUpstreamConn(sess *codexWebsocketSes
12061118 return
12071119 }
12081120 sess .conn = nil
1209- sess .connCreateSent = false
12101121 if sess .readerConn == conn {
12111122 sess .readerConn = nil
12121123 }
@@ -1273,7 +1184,6 @@ func (e *CodexWebsocketsExecutor) closeExecutionSession(sess *codexWebsocketSess
12731184 authID := sess .authID
12741185 wsURL := sess .wsURL
12751186 sess .conn = nil
1276- sess .connCreateSent = false
12771187 if sess .readerConn == conn {
12781188 sess .readerConn = nil
12791189 }
0 commit comments