@@ -20,10 +20,11 @@ var (
2020
2121// ConvertCodexResponseToGeminiParams holds parameters for response conversion.
2222type ConvertCodexResponseToGeminiParams struct {
23- Model string
24- CreatedAt int64
25- ResponseID string
26- LastStorageOutput []byte
23+ Model string
24+ CreatedAt int64
25+ ResponseID string
26+ LastStorageOutput []byte
27+ HasOutputTextDelta bool
2728}
2829
2930// ConvertCodexResponseToGemini converts Codex streaming response format to Gemini format.
@@ -42,10 +43,11 @@ type ConvertCodexResponseToGeminiParams struct {
4243func ConvertCodexResponseToGemini (_ context.Context , modelName string , originalRequestRawJSON , requestRawJSON , rawJSON []byte , param * any ) [][]byte {
4344 if * param == nil {
4445 * param = & ConvertCodexResponseToGeminiParams {
45- Model : modelName ,
46- CreatedAt : 0 ,
47- ResponseID : "" ,
48- LastStorageOutput : nil ,
46+ Model : modelName ,
47+ CreatedAt : 0 ,
48+ ResponseID : "" ,
49+ LastStorageOutput : nil ,
50+ HasOutputTextDelta : false ,
4951 }
5052 }
5153
@@ -58,18 +60,18 @@ func ConvertCodexResponseToGemini(_ context.Context, modelName string, originalR
5860 typeResult := rootResult .Get ("type" )
5961 typeStr := typeResult .String ()
6062
63+ params := (* param ).(* ConvertCodexResponseToGeminiParams )
64+
6165 // Base Gemini response template
6266 template := []byte (`{"candidates":[{"content":{"role":"model","parts":[]}}],"usageMetadata":{"trafficType":"PROVISIONED_THROUGHPUT"},"modelVersion":"gemini-2.5-pro","createTime":"2025-08-15T02:52:03.884209Z","responseId":"06CeaPH7NaCU48APvNXDyA4"}` )
63- if len ((* param ).(* ConvertCodexResponseToGeminiParams ).LastStorageOutput ) > 0 && typeStr == "response.output_item.done" {
64- template = append ([]byte (nil ), (* param ).(* ConvertCodexResponseToGeminiParams ).LastStorageOutput ... )
65- } else {
66- template , _ = sjson .SetBytes (template , "modelVersion" , (* param ).(* ConvertCodexResponseToGeminiParams ).Model )
67+ {
68+ template , _ = sjson .SetBytes (template , "modelVersion" , params .Model )
6769 createdAtResult := rootResult .Get ("response.created_at" )
6870 if createdAtResult .Exists () {
69- ( * param ).( * ConvertCodexResponseToGeminiParams ) .CreatedAt = createdAtResult .Int ()
70- template , _ = sjson .SetBytes (template , "createTime" , time .Unix (( * param ).( * ConvertCodexResponseToGeminiParams ) .CreatedAt , 0 ).Format (time .RFC3339Nano ))
71+ params .CreatedAt = createdAtResult .Int ()
72+ template , _ = sjson .SetBytes (template , "createTime" , time .Unix (params .CreatedAt , 0 ).Format (time .RFC3339Nano ))
7173 }
72- template , _ = sjson .SetBytes (template , "responseId" , ( * param ).( * ConvertCodexResponseToGeminiParams ) .ResponseID )
74+ template , _ = sjson .SetBytes (template , "responseId" , params .ResponseID )
7375 }
7476
7577 // Handle function call completion
@@ -101,7 +103,7 @@ func ConvertCodexResponseToGemini(_ context.Context, modelName string, originalR
101103 template , _ = sjson .SetRawBytes (template , "candidates.0.content.parts.-1" , functionCall )
102104 template , _ = sjson .SetBytes (template , "candidates.0.finishReason" , "STOP" )
103105
104- ( * param ).( * ConvertCodexResponseToGeminiParams ) .LastStorageOutput = append ([]byte (nil ), template ... )
106+ params .LastStorageOutput = append ([]byte (nil ), template ... )
105107
106108 // Use this return to storage message
107109 return [][]byte {}
@@ -111,15 +113,45 @@ func ConvertCodexResponseToGemini(_ context.Context, modelName string, originalR
111113 if typeStr == "response.created" { // Handle response creation - set model and response ID
112114 template , _ = sjson .SetBytes (template , "modelVersion" , rootResult .Get ("response.model" ).String ())
113115 template , _ = sjson .SetBytes (template , "responseId" , rootResult .Get ("response.id" ).String ())
114- ( * param ).( * ConvertCodexResponseToGeminiParams ) .ResponseID = rootResult .Get ("response.id" ).String ()
116+ params .ResponseID = rootResult .Get ("response.id" ).String ()
115117 } else if typeStr == "response.reasoning_summary_text.delta" { // Handle reasoning/thinking content delta
116118 part := []byte (`{"thought":true,"text":""}` )
117119 part , _ = sjson .SetBytes (part , "text" , rootResult .Get ("delta" ).String ())
118120 template , _ = sjson .SetRawBytes (template , "candidates.0.content.parts.-1" , part )
119121 } else if typeStr == "response.output_text.delta" { // Handle regular text content delta
122+ params .HasOutputTextDelta = true
120123 part := []byte (`{"text":""}` )
121124 part , _ = sjson .SetBytes (part , "text" , rootResult .Get ("delta" ).String ())
122125 template , _ = sjson .SetRawBytes (template , "candidates.0.content.parts.-1" , part )
126+ } else if typeStr == "response.output_item.done" { // Fallback: emit final message text when no delta chunks were received
127+ itemResult := rootResult .Get ("item" )
128+ if itemResult .Get ("type" ).String () != "message" || params .HasOutputTextDelta {
129+ return [][]byte {}
130+ }
131+ contentResult := itemResult .Get ("content" )
132+ if ! contentResult .Exists () || ! contentResult .IsArray () {
133+ return [][]byte {}
134+ }
135+ wroteText := false
136+ contentResult .ForEach (func (_ , partResult gjson.Result ) bool {
137+ if partResult .Get ("type" ).String () != "output_text" {
138+ return true
139+ }
140+ text := partResult .Get ("text" ).String ()
141+ if text == "" {
142+ return true
143+ }
144+ part := []byte (`{"text":""}` )
145+ part , _ = sjson .SetBytes (part , "text" , text )
146+ template , _ = sjson .SetRawBytes (template , "candidates.0.content.parts.-1" , part )
147+ wroteText = true
148+ return true
149+ })
150+ if wroteText {
151+ params .HasOutputTextDelta = true
152+ return [][]byte {template }
153+ }
154+ return [][]byte {}
123155 } else if typeStr == "response.completed" { // Handle response completion with usage metadata
124156 template , _ = sjson .SetBytes (template , "usageMetadata.promptTokenCount" , rootResult .Get ("response.usage.input_tokens" ).Int ())
125157 template , _ = sjson .SetBytes (template , "usageMetadata.candidatesTokenCount" , rootResult .Get ("response.usage.output_tokens" ).Int ())
@@ -129,11 +161,10 @@ func ConvertCodexResponseToGemini(_ context.Context, modelName string, originalR
129161 return [][]byte {}
130162 }
131163
132- if len ((* param ).(* ConvertCodexResponseToGeminiParams ).LastStorageOutput ) > 0 {
133- return [][]byte {
134- append ([]byte (nil ), (* param ).(* ConvertCodexResponseToGeminiParams ).LastStorageOutput ... ),
135- template ,
136- }
164+ if len (params .LastStorageOutput ) > 0 {
165+ stored := append ([]byte (nil ), params .LastStorageOutput ... )
166+ params .LastStorageOutput = nil
167+ return [][]byte {stored , template }
137168 }
138169 return [][]byte {template }
139170}
0 commit comments