History

Refactor translator packages for OpenAI Chat Completions

- Renamed `openai` packages to `chat_completions` across translator modules. - Introduced `openai_responses_handlers` with handlers for `/v1/models` and OpenAI-compatible chat completions endpoints. - Updated constants and registry identifiers for OpenAI response type. - Simplified request/response conversions and added detailed retry/error handling. - Added `golang.org/x/crypto` for additional cryptographic functions.

Luis Pater committed 1 year ago

Commit e3994ac1 parent bdac24b16 files · +498 −24Browse files

Unified · Split

16 files
  1. go.mod +1 −1
  2. internal/api/handlers/openai/openai_responses_handlers.go +474 −0
  3. internal/constant/constant.go +6 −6
  4. internal/translator/claude/openai/chat-completions/claude_openai_request.go +1 −1
  5. internal/translator/claude/openai/chat-completions/claude_openai_response.go +1 −1
  6. internal/translator/claude/openai/chat-completions/init.go +1 −1
  7. internal/translator/codex/openai/chat-completions/codex_openai_request.go +1 −1
  8. internal/translator/codex/openai/chat-completions/codex_openai_response.go +1 −1
  9. internal/translator/codex/openai/chat-completions/init.go +1 −1
  10. internal/translator/gemini-cli/openai/chat-completions/cli_openai_request.go +1 −1
  11. internal/translator/gemini-cli/openai/chat-completions/cli_openai_response.go +2 −2
  12. internal/translator/gemini-cli/openai/chat-completions/init.go +1 −1
  13. internal/translator/gemini/openai/chat-completions/gemini_openai_request.go +1 −1
  14. internal/translator/gemini/openai/chat-completions/gemini_openai_response.go +1 −1
  15. internal/translator/gemini/openai/chat-completions/init.go +1 −1
  16. internal/translator/init.go +4 −4
go.mod+1−1
@@ -10,6 +10,7 @@ require ( · 9 unmodified lines
10 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef84239796610 github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
11 github.com/tidwall/gjson v1.18.011 github.com/tidwall/gjson v1.18.0
12 github.com/tidwall/sjson v1.2.512 github.com/tidwall/sjson v1.2.5
13 golang.org/x/crypto v0.36.0
13 golang.org/x/net v0.37.1-0.20250305215238-2914f467731714 golang.org/x/net v0.37.1-0.20250305215238-2914f4677317
14 golang.org/x/oauth2 v0.30.015 golang.org/x/oauth2 v0.30.0
15 gopkg.in/yaml.v3 v3.0.116 gopkg.in/yaml.v3 v3.0.1
@@ -39,7 +40,6 @@ require ( · 23 unmodified lines
39 github.com/twitchyliquid64/golang-asm v0.15.1 // indirect40 github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
40 github.com/ugorji/go/codec v1.2.12 // indirect41 github.com/ugorji/go/codec v1.2.12 // indirect
41 golang.org/x/arch v0.8.0 // indirect42 golang.org/x/arch v0.8.0 // indirect
42 golang.org/x/crypto v0.36.0 // indirect
43 golang.org/x/sys v0.31.0 // indirect43 golang.org/x/sys v0.31.0 // indirect
44 golang.org/x/text v0.23.0 // indirect44 golang.org/x/text v0.23.0 // indirect
45 google.golang.org/protobuf v1.34.1 // indirect45 google.golang.org/protobuf v1.34.1 // indirect
internal/api/handlers/openai/openai_responses_handlers.go+474−0
@@ -0,0 +1,474 @@
1// Package openai provides HTTP handlers for OpenAIResponses API endpoints.
2// This package implements the OpenAIResponses-compatible API interface, including model listing
3// and chat completion functionality. It supports both streaming and non-streaming responses,
4// and manages a pool of clients to interact with backend services.
5// The handlers translate OpenAIResponses API requests to the appropriate backend format and
6// convert responses back to OpenAIResponses-compatible format.
7package openai
8
9import (
10 "context"
11 "fmt"
12 "net/http"
13 "time"
14
15 "github.com/gin-gonic/gin"
16 "github.com/luispater/CLIProxyAPI/internal/api/handlers"
17 . "github.com/luispater/CLIProxyAPI/internal/constant"
18 "github.com/luispater/CLIProxyAPI/internal/interfaces"
19 "github.com/luispater/CLIProxyAPI/internal/registry"
20 "github.com/luispater/CLIProxyAPI/internal/util"
21 log "github.com/sirupsen/logrus"
22 "github.com/tidwall/gjson"
23)
24
25// OpenAIResponsesAPIHandler contains the handlers for OpenAIResponses API endpoints.
26// It holds a pool of clients to interact with the backend service.
27type OpenAIResponsesAPIHandler struct {
28 *handlers.BaseAPIHandler
29}
30
31// NewOpenAIResponsesAPIHandler creates a new OpenAIResponses API handlers instance.
32// It takes an BaseAPIHandler instance as input and returns an OpenAIResponsesAPIHandler.
33//
34// Parameters:
35// - apiHandlers: The base API handlers instance
36//
37// Returns:
38// - *OpenAIResponsesAPIHandler: A new OpenAIResponses API handlers instance
39func NewOpenAIResponsesAPIHandler(apiHandlers *handlers.BaseAPIHandler) *OpenAIResponsesAPIHandler {
40 return &OpenAIResponsesAPIHandler{
41 BaseAPIHandler: apiHandlers,
42 }
43}
44
45// HandlerType returns the identifier for this handler implementation.
46func (h *OpenAIResponsesAPIHandler) HandlerType() string {
47 return OPENAI_RESPONSE
48}
49
50// Models returns the OpenAIResponses-compatible model metadata supported by this handler.
51func (h *OpenAIResponsesAPIHandler) Models() []map[string]any {
52 // Get dynamic models from the global registry
53 modelRegistry := registry.GetGlobalRegistry()
54 return modelRegistry.GetAvailableModels("openai")
55}
56
57// OpenAIResponsesModels handles the /v1/models endpoint.
58// It returns a list of available AI models with their capabilities
59// and specifications in OpenAIResponses-compatible format.
60func (h *OpenAIResponsesAPIHandler) OpenAIResponsesModels(c *gin.Context) {
61 c.JSON(http.StatusOK, gin.H{
62 "object": "list",
63 "data": h.Models(),
64 })
65}
66
67// ChatCompletions handles the /v1/chat/completions endpoint.
68// It determines whether the request is for a streaming or non-streaming response
69// and calls the appropriate handler based on the model provider.
70//
71// Parameters:
72// - c: The Gin context containing the HTTP request and response
73func (h *OpenAIResponsesAPIHandler) ChatCompletions(c *gin.Context) {
74 rawJSON, err := c.GetRawData()
75 // If data retrieval fails, return a 400 Bad Request error.
76 if err != nil {
77 c.JSON(http.StatusBadRequest, handlers.ErrorResponse{
78 Error: handlers.ErrorDetail{
79 Message: fmt.Sprintf("Invalid request: %v", err),
80 Type: "invalid_request_error",
81 },
82 })
83 return
84 }
85
86 // Check if the client requested a streaming response.
87 streamResult := gjson.GetBytes(rawJSON, "stream")
88 if streamResult.Type == gjson.True {
89 h.handleStreamingResponse(c, rawJSON)
90 } else {
91 h.handleNonStreamingResponse(c, rawJSON)
92 }
93
94}
95
96// Completions handles the /v1/completions endpoint.
97// It determines whether the request is for a streaming or non-streaming response
98// and calls the appropriate handler based on the model provider.
99// This endpoint follows the OpenAIResponses completions API specification.
100//
101// Parameters:
102// - c: The Gin context containing the HTTP request and response
103func (h *OpenAIResponsesAPIHandler) Completions(c *gin.Context) {
104 rawJSON, err := c.GetRawData()
105 // If data retrieval fails, return a 400 Bad Request error.
106 if err != nil {
107 c.JSON(http.StatusBadRequest, handlers.ErrorResponse{
108 Error: handlers.ErrorDetail{
109 Message: fmt.Sprintf("Invalid request: %v", err),
110 Type: "invalid_request_error",
111 },
112 })
113 return
114 }
115
116 // Check if the client requested a streaming response.
117 streamResult := gjson.GetBytes(rawJSON, "stream")
118 if streamResult.Type == gjson.True {
119 h.handleCompletionsStreamingResponse(c, rawJSON)
120 } else {
121 h.handleCompletionsNonStreamingResponse(c, rawJSON)
122 }
123
124}
125
126// handleNonStreamingResponse handles non-streaming chat completion responses
127// for Gemini models. It selects a client from the pool, sends the request, and
128// aggregates the response before sending it back to the client in OpenAIResponses format.
129//
130// Parameters:
131// - c: The Gin context containing the HTTP request and response
132// - rawJSON: The raw JSON bytes of the OpenAIResponses-compatible request
133func (h *OpenAIResponsesAPIHandler) handleNonStreamingResponse(c *gin.Context, rawJSON []byte) {
134 c.Header("Content-Type", "application/json")
135
136 modelName := gjson.GetBytes(rawJSON, "model").String()
137 cliCtx, cliCancel := h.GetContextWithCancel(h, c, context.Background())
138
139 var cliClient interfaces.Client
140 defer func() {
141 if cliClient != nil {
142 if mutex := cliClient.GetRequestMutex(); mutex != nil {
143 mutex.Unlock()
144 }
145 }
146 }()
147
148 retryCount := 0
149 for retryCount <= h.Cfg.RequestRetry {
150 var errorResponse *interfaces.ErrorMessage
151 cliClient, errorResponse = h.GetClient(modelName)
152 if errorResponse != nil {
153 c.Status(errorResponse.StatusCode)
154 _, _ = fmt.Fprint(c.Writer, errorResponse.Error.Error())
155 cliCancel()
156 return
157 }
158
159 resp, err := cliClient.SendRawMessage(cliCtx, modelName, rawJSON, "")
160 if err != nil {
161 switch err.StatusCode {
162 case 429:
163 if h.Cfg.QuotaExceeded.SwitchProject {
164 log.Debugf("quota exceeded, switch client")
165 continue // Restart the client selection process
166 }
167 case 403, 408, 500, 502, 503, 504:
168 log.Debugf("http status code %d, switch client", err.StatusCode)
169 retryCount++
170 continue
171 case 401:
172 log.Debugf("unauthorized request, try to refresh token, %s", util.HideAPIKey(cliClient.GetEmail()))
173 errRefreshTokens := cliClient.RefreshTokens(cliCtx)
174 if errRefreshTokens != nil {
175 log.Debugf("refresh token failed, switch client, %s", util.HideAPIKey(cliClient.GetEmail()))
176 }
177 retryCount++
178 continue
179 default:
180 // Forward other errors directly to the client
181 c.Status(err.StatusCode)
182 _, _ = c.Writer.Write([]byte(err.Error.Error()))
183 cliCancel(err.Error)
184 }
185 break
186 } else {
187 _, _ = c.Writer.Write(resp)
188 cliCancel(resp)
189 break
190 }
191 }
192}
193
194// handleStreamingResponse handles streaming responses for Gemini models.
195// It establishes a streaming connection with the backend service and forwards
196// the response chunks to the client in real-time using Server-Sent Events.
197//
198// Parameters:
199// - c: The Gin context containing the HTTP request and response
200// - rawJSON: The raw JSON bytes of the OpenAIResponses-compatible request
201func (h *OpenAIResponsesAPIHandler) handleStreamingResponse(c *gin.Context, rawJSON []byte) {
202 c.Header("Content-Type", "text/event-stream")
203 c.Header("Cache-Control", "no-cache")
204 c.Header("Connection", "keep-alive")
205 c.Header("Access-Control-Allow-Origin", "*")
206
207 // Get the http.Flusher interface to manually flush the response.
208 flusher, ok := c.Writer.(http.Flusher)
209 if !ok {
210 c.JSON(http.StatusInternalServerError, handlers.ErrorResponse{
211 Error: handlers.ErrorDetail{
212 Message: "Streaming not supported",
213 Type: "server_error",
214 },
215 })
216 return
217 }
218
219 modelName := gjson.GetBytes(rawJSON, "model").String()
220 cliCtx, cliCancel := h.GetContextWithCancel(h, c, context.Background())
221
222 var cliClient interfaces.Client
223 defer func() {
224 // Ensure the client's mutex is unlocked on function exit.
225 if cliClient != nil {
226 if mutex := cliClient.GetRequestMutex(); mutex != nil {
227 mutex.Unlock()
228 }
229 }
230 }()
231
232 retryCount := 0
233outLoop:
234 for retryCount <= h.Cfg.RequestRetry {
235 var errorResponse *interfaces.ErrorMessage
236 cliClient, errorResponse = h.GetClient(modelName)
237 if errorResponse != nil {
238 c.Status(errorResponse.StatusCode)
239 _, _ = fmt.Fprint(c.Writer, errorResponse.Error.Error())
240 flusher.Flush()
241 cliCancel()
242 return
243 }
244
245 // Send the message and receive response chunks and errors via channels.
246 respChan, errChan := cliClient.SendRawMessageStream(cliCtx, modelName, rawJSON, "")
247
248 for {
249 select {
250 // Handle client disconnection.
251 case <-c.Request.Context().Done():
252 if c.Request.Context().Err().Error() == "context canceled" {
253 log.Debugf("openai client disconnected: %v", c.Request.Context().Err())
254 cliCancel() // Cancel the backend request.
255 return
256 }
257 // Process incoming response chunks.
258 case chunk, okStream := <-respChan:
259 if !okStream {
260 // Stream is closed, send the final [DONE] message.
261 _, _ = fmt.Fprintf(c.Writer, "data: [DONE]\n\n")
262 flusher.Flush()
263 cliCancel()
264 return
265 }
266
267 _, _ = fmt.Fprintf(c.Writer, "data: %s\n\n", string(chunk))
268 flusher.Flush()
269 // Handle errors from the backend.
270 case err, okError := <-errChan:
271 if okError {
272 switch err.StatusCode {
273 case 429:
274 if h.Cfg.QuotaExceeded.SwitchProject {
275 log.Debugf("quota exceeded, switch client")
276 continue outLoop // Restart the client selection process
277 }
278 case 403, 408, 500, 502, 503, 504:
279 log.Debugf("http status code %d, switch client", err.StatusCode)
280 retryCount++
281 continue outLoop
282 default:
283 // Forward other errors directly to the client
284 c.Status(err.StatusCode)
285 _, _ = fmt.Fprint(c.Writer, err.Error.Error())
286 flusher.Flush()
287 cliCancel(err.Error)
288 }
289 return
290 }
291 // Send a keep-alive signal to the client.
292 case <-time.After(500 * time.Millisecond):
293 }
294 }
295 }
296}
297
298// handleCompletionsNonStreamingResponse handles non-streaming completions responses.
299// It converts completions request to chat completions format, sends to backend,
300// then converts the response back to completions format before sending to client.
301//
302// Parameters:
303// - c: The Gin context containing the HTTP request and response
304// - rawJSON: The raw JSON bytes of the OpenAIResponses-compatible completions request
305func (h *OpenAIResponsesAPIHandler) handleCompletionsNonStreamingResponse(c *gin.Context, rawJSON []byte) {
306 c.Header("Content-Type", "application/json")
307
308 // Convert completions request to chat completions format
309 chatCompletionsJSON := convertCompletionsRequestToChatCompletions(rawJSON)
310
311 modelName := gjson.GetBytes(chatCompletionsJSON, "model").String()
312 cliCtx, cliCancel := h.GetContextWithCancel(h, c, context.Background())
313
314 var cliClient interfaces.Client
315 defer func() {
316 if cliClient != nil {
317 if mutex := cliClient.GetRequestMutex(); mutex != nil {
318 mutex.Unlock()
319 }
320 }
321 }()
322
323 retryCount := 0
324 for retryCount <= h.Cfg.RequestRetry {
325 var errorResponse *interfaces.ErrorMessage
326 cliClient, errorResponse = h.GetClient(modelName)
327 if errorResponse != nil {
328 c.Status(errorResponse.StatusCode)
329 _, _ = fmt.Fprint(c.Writer, errorResponse.Error.Error())
330 cliCancel()
331 return
332 }
333
334 // Send the converted chat completions request
335 resp, err := cliClient.SendRawMessage(cliCtx, modelName, chatCompletionsJSON, "")
336 if err != nil {
337 switch err.StatusCode {
338 case 429:
339 if h.Cfg.QuotaExceeded.SwitchProject {
340 log.Debugf("quota exceeded, switch client")
341 continue // Restart the client selection process
342 }
343 case 403, 408, 500, 502, 503, 504:
344 log.Debugf("http status code %d, switch client", err.StatusCode)
345 retryCount++
346 continue
347 default:
348 // Forward other errors directly to the client
349 c.Status(err.StatusCode)
350 _, _ = c.Writer.Write([]byte(err.Error.Error()))
351 cliCancel(err.Error)
352 }
353 break
354 } else {
355 // Convert chat completions response back to completions format
356 completionsResp := convertChatCompletionsResponseToCompletions(resp)
357 _, _ = c.Writer.Write(completionsResp)
358 cliCancel(completionsResp)
359 break
360 }
361 }
362}
363
364// handleCompletionsStreamingResponse handles streaming completions responses.
365// It converts completions request to chat completions format, streams from backend,
366// then converts each response chunk back to completions format before sending to client.
367//
368// Parameters:
369// - c: The Gin context containing the HTTP request and response
370// - rawJSON: The raw JSON bytes of the OpenAIResponses-compatible completions request
371func (h *OpenAIResponsesAPIHandler) handleCompletionsStreamingResponse(c *gin.Context, rawJSON []byte) {
372 c.Header("Content-Type", "text/event-stream")
373 c.Header("Cache-Control", "no-cache")
374 c.Header("Connection", "keep-alive")
375 c.Header("Access-Control-Allow-Origin", "*")
376
377 // Get the http.Flusher interface to manually flush the response.
378 flusher, ok := c.Writer.(http.Flusher)
379 if !ok {
380 c.JSON(http.StatusInternalServerError, handlers.ErrorResponse{
381 Error: handlers.ErrorDetail{
382 Message: "Streaming not supported",
383 Type: "server_error",
384 },
385 })
386 return
387 }
388
389 // Convert completions request to chat completions format
390 chatCompletionsJSON := convertCompletionsRequestToChatCompletions(rawJSON)
391
392 modelName := gjson.GetBytes(chatCompletionsJSON, "model").String()
393 cliCtx, cliCancel := h.GetContextWithCancel(h, c, context.Background())
394
395 var cliClient interfaces.Client
396 defer func() {
397 // Ensure the client's mutex is unlocked on function exit.
398 if cliClient != nil {
399 if mutex := cliClient.GetRequestMutex(); mutex != nil {
400 mutex.Unlock()
401 }
402 }
403 }()
404
405 retryCount := 0
406outLoop:
407 for retryCount <= h.Cfg.RequestRetry {
408 var errorResponse *interfaces.ErrorMessage
409 cliClient, errorResponse = h.GetClient(modelName)
410 if errorResponse != nil {
411 c.Status(errorResponse.StatusCode)
412 _, _ = fmt.Fprint(c.Writer, errorResponse.Error.Error())
413 flusher.Flush()
414 cliCancel()
415 return
416 }
417
418 // Send the converted chat completions request and receive response chunks
419 respChan, errChan := cliClient.SendRawMessageStream(cliCtx, modelName, chatCompletionsJSON, "")
420
421 for {
422 select {
423 // Handle client disconnection.
424 case <-c.Request.Context().Done():
425 if c.Request.Context().Err().Error() == "context canceled" {
426 log.Debugf("client disconnected: %v", c.Request.Context().Err())
427 cliCancel() // Cancel the backend request.
428 return
429 }
430 // Process incoming response chunks.
431 case chunk, okStream := <-respChan:
432 if !okStream {
433 // Stream is closed, send the final [DONE] message.
434 _, _ = fmt.Fprintf(c.Writer, "data: [DONE]\n\n")
435 flusher.Flush()
436 cliCancel()
437 return
438 }
439
440 // Convert chat completions chunk to completions chunk format
441 completionsChunk := convertChatCompletionsStreamChunkToCompletions(chunk)
442 // Skip this chunk if it has no meaningful content (empty text)
443 if completionsChunk != nil {
444 _, _ = fmt.Fprintf(c.Writer, "data: %s\n\n", string(completionsChunk))
445 flusher.Flush()
446 }
447 // Handle errors from the backend.
448 case err, okError := <-errChan:
449 if okError {
450 switch err.StatusCode {
451 case 429:
452 if h.Cfg.QuotaExceeded.SwitchProject {
453 log.Debugf("quota exceeded, switch client")
454 continue outLoop // Restart the client selection process
455 }
456 case 403, 408, 500, 502, 503, 504:
457 log.Debugf("http status code %d, switch client", err.StatusCode)
458 retryCount++
459 continue outLoop
460 default:
461 // Forward other errors directly to the client
462 c.Status(err.StatusCode)
463 _, _ = fmt.Fprint(c.Writer, err.Error.Error())
464 flusher.Flush()
465 cliCancel(err.Error)
466 }
467 return
468 }
469 // Send a keep-alive signal to the client.
470 case <-time.After(500 * time.Millisecond):
471 }
472 }
473 }
474}
internal/constant/constant.go+6−6
@@ -1,10 +1,10 @@
1package constant1package constant
2 2
3const (3const (
4 GEMINI = "gemini"4 GEMINI = "gemini"
5 GEMINICLI = "gemini-cli"5 GEMINICLI = "gemini-cli"
6 CODEX = "codex"6 CODEX = "codex"
7 CLAUDE = "claude"7 CLAUDE = "claude"
8 OPENAI = "openai"8 OPENAI = "openai"
9 OPENAI_COMPATIBILITY = "openai-compatibility"9 OPENAI_RESPONSE = "openai-response"
10)10)
internal/translator/claude/openai/chat-completions/claude_openai_request.gorenamed from internal/translator/claude/openai/claude_openai_request.go+1−1
@@ -3,7 +3,7 @@ · 2 unmodified lines
3// extracting model information, system instructions, message contents, and tool declarations.3// extracting model information, system instructions, message contents, and tool declarations.
4// The package performs JSON data transformation to ensure compatibility4// The package performs JSON data transformation to ensure compatibility
5// between OpenAI API format and Claude Code API's expected format.5// between OpenAI API format and Claude Code API's expected format.
6package openai6package chat_completions
7 7
8import (8import (
9 "crypto/rand"9 "crypto/rand"
internal/translator/claude/openai/chat-completions/claude_openai_response.gorenamed from internal/translator/claude/openai/claude_openai_response.go+1−1
@@ -3,7 +3,7 @@ · 2 unmodified lines
3// JSON format, transforming streaming events and non-streaming responses into the format3// JSON format, transforming streaming events and non-streaming responses into the format
4// expected by OpenAI API clients. It supports both streaming and non-streaming modes,4// expected by OpenAI API clients. It supports both streaming and non-streaming modes,
5// handling text content, tool calls, reasoning content, and usage metadata appropriately.5// handling text content, tool calls, reasoning content, and usage metadata appropriately.
6package openai6package chat_completions
7 7
8import (8import (
9 "bufio"9 "bufio"
internal/translator/claude/openai/chat-completions/init.gorenamed from internal/translator/claude/openai/init.go+1−1
@@ -1,4 +1,4 @@
1package openai1package chat_completions
2 2
3import (3import (
4 . "github.com/luispater/CLIProxyAPI/internal/constant"4 . "github.com/luispater/CLIProxyAPI/internal/constant"
internal/translator/codex/openai/chat-completions/codex_openai_request.gorenamed from internal/translator/codex/openai/codex_openai_request.go+1−1
@@ -4,7 +4,7 @@ · 3 unmodified lines
4// The package handles the conversion of OpenAI API requests into the format4// The package handles the conversion of OpenAI API requests into the format
5// expected by the OpenAI Responses API, including proper mapping of messages,5// expected by the OpenAI Responses API, including proper mapping of messages,
6// tools, and generation parameters.6// tools, and generation parameters.
7package openai7package chat_completions
8 8
9import (9import (
10 "github.com/luispater/CLIProxyAPI/internal/misc"10 "github.com/luispater/CLIProxyAPI/internal/misc"
internal/translator/codex/openai/chat-completions/codex_openai_response.gorenamed from internal/translator/codex/openai/codex_openai_response.go+1−1
@@ -3,7 +3,7 @@ · 2 unmodified lines
3// JSON format, transforming streaming events and non-streaming responses into the format3// JSON format, transforming streaming events and non-streaming responses into the format
4// expected by OpenAI API clients. It supports both streaming and non-streaming modes,4// expected by OpenAI API clients. It supports both streaming and non-streaming modes,
5// handling text content, tool calls, reasoning content, and usage metadata appropriately.5// handling text content, tool calls, reasoning content, and usage metadata appropriately.
6package openai6package chat_completions
7 7
8import (8import (
9 "bufio"9 "bufio"
internal/translator/codex/openai/chat-completions/init.gorenamed from internal/translator/codex/openai/init.go+1−1
@@ -1,4 +1,4 @@
1package openai1package chat_completions
2 2
3import (3import (
4 . "github.com/luispater/CLIProxyAPI/internal/constant"4 . "github.com/luispater/CLIProxyAPI/internal/constant"
internal/translator/gemini-cli/openai/chat-completions/cli_openai_request.gorenamed from internal/translator/gemini-cli/openai/cli_openai_request.go+1−1
@@ -1,6 +1,6 @@
1// Package openai provides request translation functionality for OpenAI to Gemini CLI API compatibility.1// Package openai provides request translation functionality for OpenAI to Gemini CLI API compatibility.
2// It converts OpenAI Chat Completions requests into Gemini CLI compatible JSON using gjson/sjson only.2// It converts OpenAI Chat Completions requests into Gemini CLI compatible JSON using gjson/sjson only.
3package openai3package chat_completions
4 4
5import (5import (
6 "fmt"6 "fmt"
internal/translator/gemini-cli/openai/chat-completions/cli_openai_response.gorenamed from internal/translator/gemini-cli/openai/cli_openai_response.go+2−2
@@ -3,7 +3,7 @@ · 2 unmodified lines
3// JSON format, transforming streaming events and non-streaming responses into the format3// JSON format, transforming streaming events and non-streaming responses into the format
4// expected by OpenAI API clients. It supports both streaming and non-streaming modes,4// expected by OpenAI API clients. It supports both streaming and non-streaming modes,
5// handling text content, tool calls, reasoning content, and usage metadata appropriately.5// handling text content, tool calls, reasoning content, and usage metadata appropriately.
6package openai6package chat_completions
7 7
8import (8import (
9 "bytes"9 "bytes"
@@ -11,7 +11,7 @@ import ( · 1 unmodified line
11 "fmt"11 "fmt"
12 "time"12 "time"
13 13
14 . "github.com/luispater/CLIProxyAPI/internal/translator/gemini/openai"14 . "github.com/luispater/CLIProxyAPI/internal/translator/gemini/openai/chat-completions"
15 "github.com/tidwall/gjson"15 "github.com/tidwall/gjson"
16 "github.com/tidwall/sjson"16 "github.com/tidwall/sjson"
17)17)
internal/translator/gemini-cli/openai/chat-completions/init.gorenamed from internal/translator/gemini-cli/openai/init.go+1−1
@@ -1,4 +1,4 @@
1package openai1package chat_completions
2 2
3import (3import (
4 . "github.com/luispater/CLIProxyAPI/internal/constant"4 . "github.com/luispater/CLIProxyAPI/internal/constant"
internal/translator/gemini/openai/chat-completions/gemini_openai_request.gorenamed from internal/translator/gemini/openai/gemini_openai_request.go+1−1
@@ -1,6 +1,6 @@
1// Package openai provides request translation functionality for OpenAI to Gemini API compatibility.1// Package openai provides request translation functionality for OpenAI to Gemini API compatibility.
2// It converts OpenAI Chat Completions requests into Gemini compatible JSON using gjson/sjson only.2// It converts OpenAI Chat Completions requests into Gemini compatible JSON using gjson/sjson only.
3package openai3package chat_completions
4 4
5import (5import (
6 "fmt"6 "fmt"
internal/translator/gemini/openai/chat-completions/gemini_openai_response.gorenamed from internal/translator/gemini/openai/gemini_openai_response.go+1−1
@@ -3,7 +3,7 @@ · 2 unmodified lines
3// JSON format, transforming streaming events and non-streaming responses into the format3// JSON format, transforming streaming events and non-streaming responses into the format
4// expected by OpenAI API clients. It supports both streaming and non-streaming modes,4// expected by OpenAI API clients. It supports both streaming and non-streaming modes,
5// handling text content, tool calls, reasoning content, and usage metadata appropriately.5// handling text content, tool calls, reasoning content, and usage metadata appropriately.
6package openai6package chat_completions
7 7
8import (8import (
9 "bytes"9 "bytes"
internal/translator/gemini/openai/chat-completions/init.gorenamed from internal/translator/gemini/openai/init.go+1−1
@@ -1,4 +1,4 @@
1package openai1package chat_completions
2 2
3import (3import (
4 . "github.com/luispater/CLIProxyAPI/internal/constant"4 . "github.com/luispater/CLIProxyAPI/internal/constant"
internal/translator/init.go+4−4
@@ -3,18 +3,18 @@ package translator · 2 unmodified lines
3import (3import (
4 _ "github.com/luispater/CLIProxyAPI/internal/translator/claude/gemini"4 _ "github.com/luispater/CLIProxyAPI/internal/translator/claude/gemini"
5 _ "github.com/luispater/CLIProxyAPI/internal/translator/claude/gemini-cli"5 _ "github.com/luispater/CLIProxyAPI/internal/translator/claude/gemini-cli"
6 _ "github.com/luispater/CLIProxyAPI/internal/translator/claude/openai"6 _ "github.com/luispater/CLIProxyAPI/internal/translator/claude/openai/chat-completions"
7 _ "github.com/luispater/CLIProxyAPI/internal/translator/codex/claude"7 _ "github.com/luispater/CLIProxyAPI/internal/translator/codex/claude"
8 _ "github.com/luispater/CLIProxyAPI/internal/translator/codex/gemini"8 _ "github.com/luispater/CLIProxyAPI/internal/translator/codex/gemini"
9 _ "github.com/luispater/CLIProxyAPI/internal/translator/codex/gemini-cli"9 _ "github.com/luispater/CLIProxyAPI/internal/translator/codex/gemini-cli"
10 _ "github.com/luispater/CLIProxyAPI/internal/translator/codex/openai"10 _ "github.com/luispater/CLIProxyAPI/internal/translator/codex/openai/chat-completions"
11 _ "github.com/luispater/CLIProxyAPI/internal/translator/gemini-cli/claude"11 _ "github.com/luispater/CLIProxyAPI/internal/translator/gemini-cli/claude"
12 _ "github.com/luispater/CLIProxyAPI/internal/translator/gemini-cli/gemini"12 _ "github.com/luispater/CLIProxyAPI/internal/translator/gemini-cli/gemini"
13 _ "github.com/luispater/CLIProxyAPI/internal/translator/gemini-cli/openai"13 _ "github.com/luispater/CLIProxyAPI/internal/translator/gemini-cli/openai/chat-completions"
14 _ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/claude"14 _ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/claude"
15 _ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/gemini"15 _ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/gemini"
16 _ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/gemini-cli"16 _ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/gemini-cli"
17 _ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/openai"17 _ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/openai/chat-completions"
18 _ "github.com/luispater/CLIProxyAPI/internal/translator/openai/claude"18 _ "github.com/luispater/CLIProxyAPI/internal/translator/openai/claude"
19 _ "github.com/luispater/CLIProxyAPI/internal/translator/openai/gemini"19 _ "github.com/luispater/CLIProxyAPI/internal/translator/openai/gemini"
20 _ "github.com/luispater/CLIProxyAPI/internal/translator/openai/gemini-cli"20 _ "github.com/luispater/CLIProxyAPI/internal/translator/openai/gemini-cli"