// Package code provides response translation functionality for Gemini API. // This package handles the conversion of Codex backend responses into Gemini-compatible // JSON format, transforming streaming events into single-line JSON responses that include // thinking content, regular text content, and function calls in the format expected by // Gemini API clients. package code import ( "encoding/json" "time" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) type ConvertCodexResponseToGeminiParams struct { Model string CreatedAt int64 ResponseID string LastStorageOutput string } // ConvertCodexResponseToGemini converts Codex streaming response format to Gemini single-line JSON format. // This function processes various Codex event types and transforms them into Gemini-compatible JSON responses. // It handles thinking content, regular text content, and function calls, outputting single-line JSON // that matches the Gemini API response format. // The lastEventType parameter tracks the previous event type to handle consecutive function calls properly. func ConvertCodexResponseToGemini(rawJSON []byte, param *ConvertCodexResponseToGeminiParams) []string { rootResult := gjson.ParseBytes(rawJSON) typeResult := rootResult.Get("type") typeStr := typeResult.String() // Base Gemini response template template := `{"candidates":[{"content":{"role":"model","parts":[]}}],"usageMetadata":{"trafficType":"PROVISIONED_THROUGHPUT"},"modelVersion":"gemini-2.5-pro","createTime":"2025-08-15T02:52:03.884209Z","responseId":"06CeaPH7NaCU48APvNXDyA4"}` if param.LastStorageOutput != "" && typeStr == "response.output_item.done" { template = param.LastStorageOutput } else { template, _ = sjson.Set(template, "modelVersion", param.Model) createdAtResult := rootResult.Get("response.created_at") if createdAtResult.Exists() { param.CreatedAt = createdAtResult.Int() template, _ = sjson.Set(template, "createTime", time.Unix(param.CreatedAt, 0).Format(time.RFC3339Nano)) } template, _ = sjson.Set(template, "responseId", param.ResponseID) } // Handle function call completion if typeStr == "response.output_item.done" { itemResult := rootResult.Get("item") itemType := itemResult.Get("type").String() if itemType == "function_call" { // Create function call part functionCall := `{"functionCall":{"name":"","args":{}}}` functionCall, _ = sjson.Set(functionCall, "functionCall.name", itemResult.Get("name").String()) // Parse and set arguments argsStr := itemResult.Get("arguments").String() if argsStr != "" { argsResult := gjson.Parse(argsStr) if argsResult.IsObject() { functionCall, _ = sjson.SetRaw(functionCall, "functionCall.args", argsStr) } } template, _ = sjson.SetRaw(template, "candidates.0.content.parts.-1", functionCall) template, _ = sjson.Set(template, "candidates.0.finishReason", "STOP") param.LastStorageOutput = template // Use this return to storage message return []string{} } } if typeStr == "response.created" { // Handle response creation - set model and response ID template, _ = sjson.Set(template, "modelVersion", rootResult.Get("response.model").String()) template, _ = sjson.Set(template, "responseId", rootResult.Get("response.id").String()) param.ResponseID = rootResult.Get("response.id").String() } else if typeStr == "response.reasoning_summary_text.delta" { // Handle reasoning/thinking content delta part := `{"thought":true,"text":""}` part, _ = sjson.Set(part, "text", rootResult.Get("delta").String()) template, _ = sjson.SetRaw(template, "candidates.0.content.parts.-1", part) } else if typeStr == "response.output_text.delta" { // Handle regular text content delta part := `{"text":""}` part, _ = sjson.Set(part, "text", rootResult.Get("delta").String()) template, _ = sjson.SetRaw(template, "candidates.0.content.parts.-1", part) } else if typeStr == "response.completed" { // Handle response completion with usage metadata template, _ = sjson.Set(template, "usageMetadata.promptTokenCount", rootResult.Get("response.usage.input_tokens").Int()) template, _ = sjson.Set(template, "usageMetadata.candidatesTokenCount", rootResult.Get("response.usage.output_tokens").Int()) totalTokens := rootResult.Get("response.usage.input_tokens").Int() + rootResult.Get("response.usage.output_tokens").Int() template, _ = sjson.Set(template, "usageMetadata.totalTokenCount", totalTokens) } else { return []string{} } if param.LastStorageOutput != "" { return []string{param.LastStorageOutput, template} } else { return []string{template} } } // ConvertCodexResponseToGeminiNonStream converts a completed Codex response to Gemini non-streaming format. // This function processes the final response.completed event and transforms it into a complete // Gemini-compatible JSON response that includes all content parts, function calls, and usage metadata. func ConvertCodexResponseToGeminiNonStream(rawJSON []byte, model string) string { rootResult := gjson.ParseBytes(rawJSON) // Verify this is a response.completed event if rootResult.Get("type").String() != "response.completed" { return "" } // Base Gemini response template for non-streaming template := `{"candidates":[{"content":{"role":"model","parts":[]},"finishReason":"STOP"}],"usageMetadata":{"trafficType":"PROVISIONED_THROUGHPUT"},"modelVersion":"","createTime":"","responseId":""}` // Set model version template, _ = sjson.Set(template, "modelVersion", model) // Set response metadata from the completed response responseData := rootResult.Get("response") if responseData.Exists() { // Set response ID if responseId := responseData.Get("id"); responseId.Exists() { template, _ = sjson.Set(template, "responseId", responseId.String()) } // Set creation time if createdAt := responseData.Get("created_at"); createdAt.Exists() { template, _ = sjson.Set(template, "createTime", time.Unix(createdAt.Int(), 0).Format(time.RFC3339Nano)) } // Set usage metadata if usage := responseData.Get("usage"); usage.Exists() { inputTokens := usage.Get("input_tokens").Int() outputTokens := usage.Get("output_tokens").Int() totalTokens := inputTokens + outputTokens template, _ = sjson.Set(template, "usageMetadata.promptTokenCount", inputTokens) template, _ = sjson.Set(template, "usageMetadata.candidatesTokenCount", outputTokens) template, _ = sjson.Set(template, "usageMetadata.totalTokenCount", totalTokens) } // Process output content to build parts array var parts []interface{} hasToolCall := false var pendingFunctionCalls []interface{} flushPendingFunctionCalls := func() { if len(pendingFunctionCalls) > 0 { // Add all pending function calls as individual parts // This maintains the original Gemini API format while ensuring consecutive calls are grouped together for _, fc := range pendingFunctionCalls { parts = append(parts, fc) } pendingFunctionCalls = nil } } if output := responseData.Get("output"); output.Exists() && output.IsArray() { output.ForEach(func(key, value gjson.Result) bool { itemType := value.Get("type").String() switch itemType { case "reasoning": // Flush any pending function calls before adding non-function content flushPendingFunctionCalls() // Add thinking content if content := value.Get("content"); content.Exists() { part := map[string]interface{}{ "thought": true, "text": content.String(), } parts = append(parts, part) } case "message": // Flush any pending function calls before adding non-function content flushPendingFunctionCalls() // Add regular text content if content := value.Get("content"); content.Exists() && content.IsArray() { content.ForEach(func(_, contentItem gjson.Result) bool { if contentItem.Get("type").String() == "output_text" { if text := contentItem.Get("text"); text.Exists() { part := map[string]interface{}{ "text": text.String(), } parts = append(parts, part) } } return true }) } case "function_call": // Collect function call for potential merging with consecutive ones hasToolCall = true functionCall := map[string]interface{}{ "functionCall": map[string]interface{}{ "name": value.Get("name").String(), "args": map[string]interface{}{}, }, } // Parse and set arguments if argsStr := value.Get("arguments").String(); argsStr != "" { argsResult := gjson.Parse(argsStr) if argsResult.IsObject() { var args map[string]interface{} if err := json.Unmarshal([]byte(argsStr), &args); err == nil { functionCall["functionCall"].(map[string]interface{})["args"] = args } } } pendingFunctionCalls = append(pendingFunctionCalls, functionCall) } return true }) // Handle any remaining pending function calls at the end flushPendingFunctionCalls() } // Set the parts array if len(parts) > 0 { template, _ = sjson.SetRaw(template, "candidates.0.content.parts", mustMarshalJSON(parts)) } // Set finish reason based on whether there were tool calls if hasToolCall { template, _ = sjson.Set(template, "candidates.0.finishReason", "STOP") } else { template, _ = sjson.Set(template, "candidates.0.finishReason", "STOP") } } return template } // mustMarshalJSON marshals data to JSON, panicking on error (should not happen with valid data) func mustMarshalJSON(v interface{}) string { data, err := json.Marshal(v) if err != nil { panic(err) } return string(data) }