Update app.py
Browse files
app.py
CHANGED
|
@@ -62,20 +62,50 @@ MAX_RETRIES = 2
|
|
| 62 |
# "disabled" β force thinking OFF
|
| 63 |
THINKING_MODE = os.getenv("THINKING_MODE", "enabled").strip().lower()
|
| 64 |
|
| 65 |
-
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
# identity encoding β no gzip overhead on the hot SSE path
|
| 68 |
-
"Accept-Encoding":
|
| 69 |
-
"Accept":
|
| 70 |
-
"Origin":
|
| 71 |
-
"Referer":
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
}
|
| 78 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
STREAM_RESPONSE_HEADERS = {
|
| 80 |
"Cache-Control": "no-cache",
|
| 81 |
"Connection": "keep-alive",
|
|
@@ -89,13 +119,13 @@ STREAM_RESPONSE_HEADERS = {
|
|
| 89 |
|
| 90 |
MODEL_MAP = {
|
| 91 |
"claude-sonnet-4-6": "nemotron-3-super-free",
|
| 92 |
-
"claude-sonnet-4-5": "
|
| 93 |
"claude-haiku-4-6": "deepseek-v4-flash-free",
|
| 94 |
"claude-haiku-4-5": "deepseek-v4-flash-free",
|
| 95 |
-
"claude-haiku-4-5-20251001": "
|
| 96 |
"claude-opus-4-6": "nemotron-3-super-free",
|
| 97 |
}
|
| 98 |
-
DEFAULT_MODEL = "
|
| 99 |
|
| 100 |
_MODELS_CACHE = {
|
| 101 |
"object": "list",
|
|
@@ -754,7 +784,7 @@ async def messages(
|
|
| 754 |
body, openai_messages, openai_tools,
|
| 755 |
openai_tool_choice, mapped_model, stream,
|
| 756 |
)
|
| 757 |
-
headers =
|
| 758 |
|
| 759 |
logger.info(
|
| 760 |
"[%s] %sβ%s stream=%s tools=%d thinking=%s msgs=%d",
|
|
@@ -778,6 +808,20 @@ async def messages(
|
|
| 778 |
)
|
| 779 |
response = await client.send(upstream_req, stream=True)
|
| 780 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 781 |
if response.status_code != 200:
|
| 782 |
raw_err = (await response.aread()).decode(errors="ignore")
|
| 783 |
logger.warning("[%s] upstream %d: %s",
|
|
@@ -798,6 +842,19 @@ async def messages(
|
|
| 798 |
response = await client.post(
|
| 799 |
UPSTREAM_URL, json=payload, headers=headers
|
| 800 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 801 |
if response.status_code != 200:
|
| 802 |
logger.warning("[%s] upstream %d", request_id, response.status_code)
|
| 803 |
last_error = response.text
|
|
|
|
| 62 |
# "disabled" β force thinking OFF
|
| 63 |
THINKING_MODE = os.getenv("THINKING_MODE", "enabled").strip().lower()
|
| 64 |
|
| 65 |
+
# opencode CLI version to impersonate β update if the CLI releases a new version
|
| 66 |
+
_OPENCODE_VERSION = os.getenv("OPENCODE_VERSION", "1.3.15")
|
| 67 |
+
|
| 68 |
+
# Static headers β same for every request
|
| 69 |
+
_STATIC_HEADERS = {
|
| 70 |
+
"Content-Type": "application/json",
|
| 71 |
# identity encoding β no gzip overhead on the hot SSE path
|
| 72 |
+
"Accept-Encoding": "identity",
|
| 73 |
+
"Accept": "application/json",
|
| 74 |
+
"Origin": "https://opencode.ai",
|
| 75 |
+
"Referer": "https://opencode.ai/",
|
| 76 |
+
# Impersonate the official opencode CLI User-Agent
|
| 77 |
+
# Without this, Zen treats the client as anonymous β very low rate limits
|
| 78 |
+
"User-Agent": f"opencode/latest/{_OPENCODE_VERSION}/cli",
|
| 79 |
+
# Tell Zen we are the official CLI client
|
| 80 |
+
"x-opencode-client": "cli",
|
| 81 |
}
|
| 82 |
|
| 83 |
+
# One stable project-ID per process boot (mimics CLI behaviour:
|
| 84 |
+
# same project across requests within a session)
|
| 85 |
+
_PROCESS_PROJECT_ID = uuid.uuid4().hex
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def _make_upstream_headers(api_key: str) -> dict:
|
| 89 |
+
"""
|
| 90 |
+
Build a full set of upstream headers for one request.
|
| 91 |
+
|
| 92 |
+
The opencode Zen backend rate-limits by client identity.
|
| 93 |
+
It grants the full 'dailyRequests' quota only to requests
|
| 94 |
+
that carry the four x-opencode-* headers the official CLI sends.
|
| 95 |
+
Without them requests fall back to a much smaller 'fallbackValue' limit.
|
| 96 |
+
|
| 97 |
+
x-opencode-session and x-opencode-request are re-randomised every
|
| 98 |
+
call (matching CLI behaviour); x-opencode-project is stable per
|
| 99 |
+
process boot.
|
| 100 |
+
"""
|
| 101 |
+
return {
|
| 102 |
+
**_STATIC_HEADERS,
|
| 103 |
+
"x-api-key": api_key,
|
| 104 |
+
"x-opencode-session": uuid.uuid4().hex,
|
| 105 |
+
"x-opencode-project": _PROCESS_PROJECT_ID,
|
| 106 |
+
"x-opencode-request": uuid.uuid4().hex,
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
STREAM_RESPONSE_HEADERS = {
|
| 110 |
"Cache-Control": "no-cache",
|
| 111 |
"Connection": "keep-alive",
|
|
|
|
| 119 |
|
| 120 |
MODEL_MAP = {
|
| 121 |
"claude-sonnet-4-6": "nemotron-3-super-free",
|
| 122 |
+
"claude-sonnet-4-5": "deepseek-v4-flash-free",
|
| 123 |
"claude-haiku-4-6": "deepseek-v4-flash-free",
|
| 124 |
"claude-haiku-4-5": "deepseek-v4-flash-free",
|
| 125 |
+
"claude-haiku-4-5-20251001": "deepseek-v4-flash-free",
|
| 126 |
"claude-opus-4-6": "nemotron-3-super-free",
|
| 127 |
}
|
| 128 |
+
DEFAULT_MODEL = "deepseek-v4-flash-free"
|
| 129 |
|
| 130 |
_MODELS_CACHE = {
|
| 131 |
"object": "list",
|
|
|
|
| 784 |
body, openai_messages, openai_tools,
|
| 785 |
openai_tool_choice, mapped_model, stream,
|
| 786 |
)
|
| 787 |
+
headers = _make_upstream_headers(OPENCODE_API_KEY)
|
| 788 |
|
| 789 |
logger.info(
|
| 790 |
"[%s] %sβ%s stream=%s tools=%d thinking=%s msgs=%d",
|
|
|
|
| 808 |
)
|
| 809 |
response = await client.send(upstream_req, stream=True)
|
| 810 |
|
| 811 |
+
if response.status_code == 429:
|
| 812 |
+
retry_after = float(response.headers.get("retry-after", "5"))
|
| 813 |
+
raw_err = (await response.aread()).decode(errors="ignore")
|
| 814 |
+
logger.warning(
|
| 815 |
+
"[%s] 429 rate-limited β retry-after=%.1fs body=%s",
|
| 816 |
+
request_id, retry_after, raw_err[:200]
|
| 817 |
+
)
|
| 818 |
+
last_error = raw_err
|
| 819 |
+
await response.aclose()
|
| 820 |
+
await asyncio.sleep(retry_after)
|
| 821 |
+
# refresh per-request headers (new random IDs)
|
| 822 |
+
headers = _make_upstream_headers(OPENCODE_API_KEY)
|
| 823 |
+
continue
|
| 824 |
+
|
| 825 |
if response.status_code != 200:
|
| 826 |
raw_err = (await response.aread()).decode(errors="ignore")
|
| 827 |
logger.warning("[%s] upstream %d: %s",
|
|
|
|
| 842 |
response = await client.post(
|
| 843 |
UPSTREAM_URL, json=payload, headers=headers
|
| 844 |
)
|
| 845 |
+
|
| 846 |
+
if response.status_code == 429:
|
| 847 |
+
retry_after = float(response.headers.get("retry-after", "5"))
|
| 848 |
+
logger.warning(
|
| 849 |
+
"[%s] 429 rate-limited β retry-after=%.1fs",
|
| 850 |
+
request_id, retry_after
|
| 851 |
+
)
|
| 852 |
+
last_error = response.text
|
| 853 |
+
await asyncio.sleep(retry_after)
|
| 854 |
+
# refresh per-request headers (new random IDs)
|
| 855 |
+
headers = _make_upstream_headers(OPENCODE_API_KEY)
|
| 856 |
+
continue
|
| 857 |
+
|
| 858 |
if response.status_code != 200:
|
| 859 |
logger.warning("[%s] upstream %d", request_id, response.status_code)
|
| 860 |
last_error = response.text
|