File size: 5,818 Bytes
d12a6b6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32a0c88
 
d12a6b6
32a0c88
d12a6b6
 
 
 
 
32a0c88
d12a6b6
 
 
 
 
 
 
 
 
 
 
 
 
32a0c88
d12a6b6
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
"""
OpenAI API Routes - Handles OpenAI-compatible endpoints.
This module provides OpenAI-compatible endpoints that transform requests/responses
and delegate to the Google API client.
"""
import json
import uuid
import asyncio
from fastapi import APIRouter, Request, Response, Depends
from fastapi.responses import StreamingResponse

from .auth import authenticate_user
from .models import OpenAIChatCompletionRequest
from .openai_transformers import (
    openai_request_to_gemini,
    gemini_response_to_openai,
    gemini_stream_chunk_to_openai
)
from .google_api_client import send_gemini_request, build_gemini_payload_from_openai

router = APIRouter()


@router.post("/v1/chat/completions")
async def openai_chat_completions(
    request: OpenAIChatCompletionRequest, 
    http_request: Request, 
    username: str = Depends(authenticate_user)
):
    """
    OpenAI-compatible chat completions endpoint.
    Transforms OpenAI requests to Gemini format, sends to Google API, 
    and transforms responses back to OpenAI format.
    """
    
    # Transform OpenAI request to Gemini format
    gemini_request_data = openai_request_to_gemini(request)
    
    # Build the payload for Google API
    gemini_payload = build_gemini_payload_from_openai(gemini_request_data)
    
    if request.stream:
        # Handle streaming response
        async def openai_stream_generator():
            response = send_gemini_request(gemini_payload, is_streaming=True)
            
            if isinstance(response, StreamingResponse):
                response_id = "chatcmpl-" + str(uuid.uuid4())
                
                async for chunk in response.body_iterator:
                    if isinstance(chunk, bytes):
                        chunk = chunk.decode('utf-8')
                    
                    if chunk.startswith('data: '):
                        try:
                            # Parse the Gemini streaming chunk
                            chunk_data = chunk[6:]  # Remove 'data: ' prefix
                            gemini_chunk = json.loads(chunk_data)
                            
                            # Transform to OpenAI format
                            openai_chunk = gemini_stream_chunk_to_openai(
                                gemini_chunk, 
                                request.model, 
                                response_id
                            )
                            
                            # Send as OpenAI streaming format
                            yield f"data: {json.dumps(openai_chunk)}\n\n"
                            await asyncio.sleep(0)
                            
                        except (json.JSONDecodeError, KeyError, UnicodeDecodeError) as e:
                            continue
                
                # Send the final [DONE] marker
                yield "data: [DONE]\n\n"
            else:
                # Error case - forward the error response
                error_data = {
                    "error": {
                        "message": "Streaming request failed",
                        "type": "api_error"
                    }
                }
                yield f"data: {json.dumps(error_data)}\n\n"
                yield "data: [DONE]\n\n"

        return StreamingResponse(
            openai_stream_generator(), 
            media_type="text/event-stream"
        )
    
    else:
        # Handle non-streaming response
        response = send_gemini_request(gemini_payload, is_streaming=False)
        
        if isinstance(response, Response) and response.status_code != 200:
            # Forward error responses as-is
            return response
        
        try:
            # Parse Gemini response and transform to OpenAI format
            gemini_response = json.loads(response.body)
            openai_response = gemini_response_to_openai(gemini_response, request.model)
            
            return openai_response
            
        except (json.JSONDecodeError, AttributeError) as e:
            return Response(
                content=json.dumps({
                    "error": {
                        "message": "Failed to process response",
                        "type": "api_error"
                    }
                }),
                status_code=500,
                media_type="application/json"
            )


@router.get("/v1/models")
async def openai_list_models(username: str = Depends(authenticate_user)):
    """
    OpenAI-compatible models endpoint.
    Returns available models in OpenAI format.
    """
    
    # Convert our Gemini models to OpenAI format
    from .config import SUPPORTED_MODELS
    
    openai_models = []
    for model in SUPPORTED_MODELS:
        # Remove "models/" prefix for OpenAI compatibility
        model_id = model["name"].replace("models/", "")
        openai_models.append({
            "id": model_id,
            "object": "model",
            "created": 1677610602,  # Static timestamp
            "owned_by": "google",
            "permission": [
                {
                    "id": "modelperm-" + model_id.replace("/", "-"),
                    "object": "model_permission",
                    "created": 1677610602,
                    "allow_create_engine": False,
                    "allow_sampling": True,
                    "allow_logprobs": False,
                    "allow_search_indices": False,
                    "allow_view": True,
                    "allow_fine_tuning": False,
                    "organization": "*",
                    "group": None,
                    "is_blocking": False
                }
            ],
            "root": model_id,
            "parent": None
        })
    
    return {
        "object": "list",
        "data": openai_models
    }