File size: 5,786 Bytes
35205e8 | 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 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | from __future__ import annotations
import json
import time
from typing import Any, Dict, List, Tuple
from urllib.parse import urlparse, urlunparse
import requests
from flask import Response, current_app, jsonify, make_response
from .config import CHATGPT_RESPONSES_URL
from .http import build_cors_headers
from .model_registry import normalize_model_name
from .session import ensure_session_id
from flask import request as flask_request
from .utils import get_effective_chatgpt_auth
def _log_json(prefix: str, payload: Any) -> None:
try:
print(f"{prefix}\n{json.dumps(payload, indent=2, ensure_ascii=False)}")
except Exception:
try:
print(f"{prefix}\n{payload}")
except Exception:
pass
def start_upstream_request(
model: str,
input_items: List[Dict[str, Any]],
*,
instructions: str | None = None,
tools: List[Dict[str, Any]] | None = None,
tool_choice: Any | None = None,
parallel_tool_calls: bool = False,
reasoning_param: Dict[str, Any] | None = None,
service_tier: str | None = None,
):
access_token, account_id = get_effective_chatgpt_auth()
if not access_token or not account_id:
resp = make_response(
jsonify(
{
"error": {
"message": "Missing ChatGPT credentials. Run 'python3 chatmock.py login' first.",
}
}
),
401,
)
for k, v in build_cors_headers().items():
resp.headers.setdefault(k, v)
return None, resp
include: List[str] = []
if isinstance(reasoning_param, dict):
include.append("reasoning.encrypted_content")
client_session_id = None
try:
client_session_id = (
flask_request.headers.get("X-Session-Id")
or flask_request.headers.get("session_id")
or None
)
except Exception:
client_session_id = None
session_id = ensure_session_id(instructions, input_items, client_session_id)
responses_payload = {
"model": model,
"instructions": instructions if isinstance(instructions, str) and instructions.strip() else instructions,
"input": input_items,
"tools": tools or [],
"tool_choice": tool_choice if tool_choice in ("auto", "none") or isinstance(tool_choice, dict) else "auto",
"parallel_tool_calls": bool(parallel_tool_calls),
"store": False,
"stream": True,
"prompt_cache_key": session_id,
}
if include:
responses_payload["include"] = include
if reasoning_param is not None:
responses_payload["reasoning"] = reasoning_param
if isinstance(service_tier, str) and service_tier.strip():
responses_payload["service_tier"] = service_tier.strip().lower()
return start_upstream_raw_request(
responses_payload,
session_id=session_id,
stream=True,
)
def build_upstream_headers(
access_token: str,
account_id: str,
session_id: str,
*,
accept: str = "text/event-stream",
) -> Dict[str, str]:
return {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json",
"Accept": accept,
"chatgpt-account-id": account_id,
"OpenAI-Beta": "responses=experimental",
"session_id": session_id,
}
def start_upstream_raw_request(
responses_payload: Dict[str, Any],
*,
session_id: str | None = None,
stream: bool = True,
):
access_token, account_id = get_effective_chatgpt_auth()
if not access_token or not account_id:
resp = make_response(
jsonify(
{
"error": {
"message": "Missing ChatGPT credentials. Run 'python3 chatmock.py login' first.",
}
}
),
401,
)
for k, v in build_cors_headers().items():
resp.headers.setdefault(k, v)
return None, resp
effective_session_id = session_id
if not isinstance(effective_session_id, str) or not effective_session_id.strip():
payload_prompt_cache_key = responses_payload.get("prompt_cache_key")
if isinstance(payload_prompt_cache_key, str) and payload_prompt_cache_key.strip():
effective_session_id = payload_prompt_cache_key.strip()
if not isinstance(effective_session_id, str) or not effective_session_id.strip():
effective_session_id = str(int(time.time() * 1000))
verbose = False
try:
verbose = bool(current_app.config.get("VERBOSE"))
except Exception:
verbose = False
if verbose:
_log_json("OUTBOUND >> ChatGPT Responses API payload", responses_payload)
headers = build_upstream_headers(
access_token,
account_id,
effective_session_id,
accept=("text/event-stream" if stream else "application/json"),
)
try:
upstream = requests.post(
CHATGPT_RESPONSES_URL,
headers=headers,
json=responses_payload,
stream=stream,
timeout=600,
)
except requests.RequestException as e:
resp = make_response(jsonify({"error": {"message": f"Upstream ChatGPT request failed: {e}"}}), 502)
for k, v in build_cors_headers().items():
resp.headers.setdefault(k, v)
return None, resp
return upstream, None
def build_upstream_websocket_url() -> str:
parsed = urlparse(CHATGPT_RESPONSES_URL)
scheme = parsed.scheme.lower()
if scheme == "https":
parsed = parsed._replace(scheme="wss")
elif scheme == "http":
parsed = parsed._replace(scheme="ws")
return urlunparse(parsed)
|