// Package openai provides response translation functionality for Codex to OpenAI API compatibility. // This package handles the conversion of Codex API responses into OpenAI Chat Completions-compatible // JSON format, transforming streaming events and non-streaming responses into the format // expected by OpenAI API clients. It supports both streaming and non-streaming modes, // handling text content, tool calls, reasoning content, and usage metadata appropriately. package chat_completions import ( "bufio" "bytes" "context" "time" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) var ( dataTag = []byte("data: ") ) // ConvertCliToOpenAIParams holds parameters for response conversion. type ConvertCliToOpenAIParams struct { ResponseID string CreatedAt int64 Model string } // ConvertCodexResponseToOpenAI translates a single chunk of a streaming response from the // Codex API format to the OpenAI Chat Completions streaming format. // It processes various Codex event types and transforms them into OpenAI-compatible JSON responses. // The function handles text content, tool calls, reasoning content, and usage metadata, outputting // responses that match the OpenAI API format. It supports incremental updates for streaming responses. // // Parameters: // - ctx: The context for the request, used for cancellation and timeout handling // - modelName: The name of the model being used for the response // - rawJSON: The raw JSON response from the Codex API // - param: A pointer to a parameter object for maintaining state between calls // // Returns: // - []string: A slice of strings, each containing an OpenAI-compatible JSON response func ConvertCodexResponseToOpenAI(_ context.Context, modelName string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) []string { if *param == nil { *param = &ConvertCliToOpenAIParams{ Model: modelName, CreatedAt: 0, ResponseID: "", } } if !bytes.HasPrefix(rawJSON, dataTag) { return []string{} } rawJSON = rawJSON[6:] // Initialize the OpenAI SSE template. template := `{"id":"","object":"chat.completion.chunk","created":12345,"model":"model","choices":[{"index":0,"delta":{"role":null,"content":null,"reasoning_content":null,"tool_calls":null},"finish_reason":null,"native_finish_reason":null}]}` rootResult := gjson.ParseBytes(rawJSON) typeResult := rootResult.Get("type") dataType := typeResult.String() if dataType == "response.created" { (*param).(*ConvertCliToOpenAIParams).ResponseID = rootResult.Get("response.id").String() (*param).(*ConvertCliToOpenAIParams).CreatedAt = rootResult.Get("response.created_at").Int() (*param).(*ConvertCliToOpenAIParams).Model = rootResult.Get("response.model").String() return []string{} } // Extract and set the model version. if modelResult := gjson.GetBytes(rawJSON, "model"); modelResult.Exists() { template, _ = sjson.Set(template, "model", modelResult.String()) } template, _ = sjson.Set(template, "created", (*param).(*ConvertCliToOpenAIParams).CreatedAt) // Extract and set the response ID. template, _ = sjson.Set(template, "id", (*param).(*ConvertCliToOpenAIParams).ResponseID) // Extract and set usage metadata (token counts). if usageResult := gjson.GetBytes(rawJSON, "response.usage"); usageResult.Exists() { if outputTokensResult := usageResult.Get("output_tokens"); outputTokensResult.Exists() { template, _ = sjson.Set(template, "usage.completion_tokens", outputTokensResult.Int()) } if totalTokensResult := usageResult.Get("total_tokens"); totalTokensResult.Exists() { template, _ = sjson.Set(template, "usage.total_tokens", totalTokensResult.Int()) } if inputTokensResult := usageResult.Get("input_tokens"); inputTokensResult.Exists() { template, _ = sjson.Set(template, "usage.prompt_tokens", inputTokensResult.Int()) } if reasoningTokensResult := usageResult.Get("output_tokens_details.reasoning_tokens"); reasoningTokensResult.Exists() { template, _ = sjson.Set(template, "usage.completion_tokens_details.reasoning_tokens", reasoningTokensResult.Int()) } } if dataType == "response.reasoning_summary_text.delta" { if deltaResult := rootResult.Get("delta"); deltaResult.Exists() { template, _ = sjson.Set(template, "choices.0.delta.role", "assistant") template, _ = sjson.Set(template, "choices.0.delta.reasoning_content", deltaResult.String()) } } else if dataType == "response.reasoning_summary_text.done" { template, _ = sjson.Set(template, "choices.0.delta.role", "assistant") template, _ = sjson.Set(template, "choices.0.delta.reasoning_content", "\n\n") } else if dataType == "response.output_text.delta" { if deltaResult := rootResult.Get("delta"); deltaResult.Exists() { template, _ = sjson.Set(template, "choices.0.delta.role", "assistant") template, _ = sjson.Set(template, "choices.0.delta.content", deltaResult.String()) } } else if dataType == "response.completed" { template, _ = sjson.Set(template, "choices.0.finish_reason", "stop") template, _ = sjson.Set(template, "choices.0.native_finish_reason", "stop") } else if dataType == "response.output_item.done" { functionCallItemTemplate := `{"id": "","type": "function","function": {"name": "","arguments": ""}}` itemResult := rootResult.Get("item") if itemResult.Exists() { if itemResult.Get("type").String() != "function_call" { return []string{} } template, _ = sjson.SetRaw(template, "choices.0.delta.tool_calls", `[]`) functionCallItemTemplate, _ = sjson.Set(functionCallItemTemplate, "id", itemResult.Get("call_id").String()) functionCallItemTemplate, _ = sjson.Set(functionCallItemTemplate, "function.name", itemResult.Get("name").String()) functionCallItemTemplate, _ = sjson.Set(functionCallItemTemplate, "function.arguments", itemResult.Get("arguments").String()) template, _ = sjson.Set(template, "choices.0.delta.role", "assistant") template, _ = sjson.SetRaw(template, "choices.0.delta.tool_calls.-1", functionCallItemTemplate) } } else { return []string{} } return []string{template} } // ConvertCodexResponseToOpenAINonStream converts a non-streaming Codex response to a non-streaming OpenAI response. // This function processes the complete Codex response and transforms it into a single OpenAI-compatible // JSON response. It handles message content, tool calls, reasoning content, and usage metadata, combining all // the information into a single response that matches the OpenAI API format. // // Parameters: // - ctx: The context for the request, used for cancellation and timeout handling // - modelName: The name of the model being used for the response (unused in current implementation) // - rawJSON: The raw JSON response from the Codex API // - param: A pointer to a parameter object for the conversion (unused in current implementation) // // Returns: // - string: An OpenAI-compatible JSON response containing all message content and metadata func ConvertCodexResponseToOpenAINonStream(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) string { scanner := bufio.NewScanner(bytes.NewReader(rawJSON)) buffer := make([]byte, 10240*1024) scanner.Buffer(buffer, 10240*1024) for scanner.Scan() { line := scanner.Bytes() // log.Debug(string(line)) if !bytes.HasPrefix(line, dataTag) { continue } rawJSON = line[6:] rootResult := gjson.ParseBytes(rawJSON) // Verify this is a response.completed event if rootResult.Get("type").String() != "response.completed" { continue } unixTimestamp := time.Now().Unix() responseResult := rootResult.Get("response") template := `{"id":"","object":"chat.completion","created":123456,"model":"model","choices":[{"index":0,"message":{"role":"assistant","content":null,"reasoning_content":null,"tool_calls":null},"finish_reason":null,"native_finish_reason":null}]}` // Extract and set the model version. if modelResult := responseResult.Get("model"); modelResult.Exists() { template, _ = sjson.Set(template, "model", modelResult.String()) } // Extract and set the creation timestamp. if createdAtResult := responseResult.Get("created_at"); createdAtResult.Exists() { template, _ = sjson.Set(template, "created", createdAtResult.Int()) } else { template, _ = sjson.Set(template, "created", unixTimestamp) } // Extract and set the response ID. if idResult := responseResult.Get("id"); idResult.Exists() { template, _ = sjson.Set(template, "id", idResult.String()) } // Extract and set usage metadata (token counts). if usageResult := responseResult.Get("usage"); usageResult.Exists() { if outputTokensResult := usageResult.Get("output_tokens"); outputTokensResult.Exists() { template, _ = sjson.Set(template, "usage.completion_tokens", outputTokensResult.Int()) } if totalTokensResult := usageResult.Get("total_tokens"); totalTokensResult.Exists() { template, _ = sjson.Set(template, "usage.total_tokens", totalTokensResult.Int()) } if inputTokensResult := usageResult.Get("input_tokens"); inputTokensResult.Exists() { template, _ = sjson.Set(template, "usage.prompt_tokens", inputTokensResult.Int()) } if reasoningTokensResult := usageResult.Get("output_tokens_details.reasoning_tokens"); reasoningTokensResult.Exists() { template, _ = sjson.Set(template, "usage.completion_tokens_details.reasoning_tokens", reasoningTokensResult.Int()) } } // Process the output array for content and function calls outputResult := responseResult.Get("output") if outputResult.IsArray() { outputArray := outputResult.Array() var contentText string var reasoningText string var toolCalls []string for _, outputItem := range outputArray { outputType := outputItem.Get("type").String() switch outputType { case "reasoning": // Extract reasoning content from summary if summaryResult := outputItem.Get("summary"); summaryResult.IsArray() { summaryArray := summaryResult.Array() for _, summaryItem := range summaryArray { if summaryItem.Get("type").String() == "summary_text" { reasoningText = summaryItem.Get("text").String() break } } } case "message": // Extract message content if contentResult := outputItem.Get("content"); contentResult.IsArray() { contentArray := contentResult.Array() for _, contentItem := range contentArray { if contentItem.Get("type").String() == "output_text" { contentText = contentItem.Get("text").String() break } } } case "function_call": // Handle function call content functionCallTemplate := `{"id": "","type": "function","function": {"name": "","arguments": ""}}` if callIdResult := outputItem.Get("call_id"); callIdResult.Exists() { functionCallTemplate, _ = sjson.Set(functionCallTemplate, "id", callIdResult.String()) } if nameResult := outputItem.Get("name"); nameResult.Exists() { functionCallTemplate, _ = sjson.Set(functionCallTemplate, "function.name", nameResult.String()) } if argsResult := outputItem.Get("arguments"); argsResult.Exists() { functionCallTemplate, _ = sjson.Set(functionCallTemplate, "function.arguments", argsResult.String()) } toolCalls = append(toolCalls, functionCallTemplate) } } // Set content and reasoning content if found if contentText != "" { template, _ = sjson.Set(template, "choices.0.message.content", contentText) template, _ = sjson.Set(template, "choices.0.message.role", "assistant") } if reasoningText != "" { template, _ = sjson.Set(template, "choices.0.message.reasoning_content", reasoningText) template, _ = sjson.Set(template, "choices.0.message.role", "assistant") } // Add tool calls if any if len(toolCalls) > 0 { template, _ = sjson.SetRaw(template, "choices.0.message.tool_calls", `[]`) for _, toolCall := range toolCalls { template, _ = sjson.SetRaw(template, "choices.0.message.tool_calls.-1", toolCall) } template, _ = sjson.Set(template, "choices.0.message.role", "assistant") } } // Extract and set the finish reason based on status if statusResult := responseResult.Get("status"); statusResult.Exists() { status := statusResult.String() if status == "completed" { template, _ = sjson.Set(template, "choices.0.finish_reason", "stop") template, _ = sjson.Set(template, "choices.0.native_finish_reason", "stop") } } return template } return "" }