bahi-bh commited on
Commit
b3fdf5d
Β·
verified Β·
1 Parent(s): 2e66abc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +72 -15
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
- UPSTREAM_HEADERS = {
66
- "Content-Type": "application/json",
 
 
 
 
67
  # identity encoding β†’ no gzip overhead on the hot SSE path
68
- "Accept-Encoding": "identity",
69
- "Accept": "application/json",
70
- "Origin": "https://opencode.ai",
71
- "Referer": "https://opencode.ai/",
72
- "User-Agent": (
73
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
74
- "AppleWebKit/537.36 (KHTML, like Gecko) "
75
- "Chrome/124.0 Safari/537.36"
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": "nemotron-3-super-free",
93
  "claude-haiku-4-6": "deepseek-v4-flash-free",
94
  "claude-haiku-4-5": "deepseek-v4-flash-free",
95
- "claude-haiku-4-5-20251001": "nemotron-3-super-free",
96
  "claude-opus-4-6": "nemotron-3-super-free",
97
  }
98
- DEFAULT_MODEL = "nemotron-3-super-free"
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 = {**UPSTREAM_HEADERS, "x-api-key": OPENCODE_API_KEY}
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