proxy.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. #!/usr/bin/env python3
  2. from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
  3. import urllib.request
  4. import urllib.error
  5. import json
  6. # ============================================================
  7. # Agnes 配置
  8. # ============================================================
  9. AGNES_URL = "https://apihub.agnes-ai.com/v1/responses"
  10. AGNES_KEY = "sk-***"
  11. # ============================================================
  12. # Agnes 支持的 Tool 类型
  13. # ============================================================
  14. # SUPPORTED_TOOLS = {
  15. # "function",
  16. # "web_search_preview",
  17. # "code_interpreter",
  18. # "mcp",
  19. # }
  20. SUPPORTED_TOOLS = {
  21. "function",
  22. "web_search_preview",
  23. # "web_search",
  24. "code_interpreter",
  25. "file_search",
  26. "computer_use",
  27. "mcp",
  28. # "tool_search",
  29. "namespace",
  30. }
  31. # ============================================================
  32. # HTTP Handler
  33. # ============================================================
  34. class Handler(BaseHTTPRequestHandler):
  35. protocol_version = "HTTP/1.1"
  36. def do_POST(self):
  37. # ----------------------------------------------------
  38. # 只处理 /v1/responses
  39. # ----------------------------------------------------
  40. if self.path != "/v1/responses":
  41. self.send_error(404)
  42. return
  43. # ----------------------------------------------------
  44. # 读取请求
  45. # ----------------------------------------------------
  46. try:
  47. length = int(self.headers.get("Content-Length", 0))
  48. body = self.rfile.read(length)
  49. data = json.loads(body)
  50. except Exception as e:
  51. print("\n===== REQUEST PARSE ERROR =====")
  52. print(repr(e))
  53. self.send_response(400)
  54. self.send_header("Content-Type", "application/json")
  55. self.send_header("Connection", "close")
  56. self.end_headers()
  57. error = json.dumps({
  58. "error": {
  59. "message": f"Invalid JSON request: {e}",
  60. "type": "invalid_request_error"
  61. }
  62. }).encode("utf-8")
  63. self.wfile.write(error)
  64. return
  65. # ====================================================
  66. # 1. Model 映射
  67. # ====================================================
  68. original_model = data.get("model")
  69. if original_model == "gpt-5.5":
  70. data["model"] = "agnes-3.0-flash"
  71. print("\n===== MODEL =====")
  72. print("Codex :", original_model)
  73. print("Agnes :", data.get("model"))
  74. # ====================================================
  75. # 2. Tool 过滤
  76. #
  77. # Agnes 当前支持:
  78. #
  79. # function
  80. # web_search_preview
  81. # code_interpreter
  82. # mcp
  83. #
  84. # Codex 可能发送:
  85. #
  86. # custom
  87. # tool_search
  88. # 以及其他内部工具
  89. #
  90. # 对不支持的类型直接删除
  91. # ====================================================
  92. if isinstance(data.get("tools"), list):
  93. old_tools = data["tools"]
  94. new_tools = []
  95. print("\n===== TOOLS =====")
  96. for i, tool in enumerate(old_tools):
  97. tool_type = tool.get("type")
  98. if tool_type in SUPPORTED_TOOLS:
  99. new_tools.append(tool)
  100. print(
  101. f"[{i}] {tool_type:<25} KEEP"
  102. )
  103. else:
  104. print(
  105. f"[{i}] {str(tool_type):<25} DROP"
  106. )
  107. data["tools"] = new_tools
  108. # ------------------------------------------------
  109. # 如果已经没有任何 tools
  110. # Agnes 不允许:
  111. #
  112. # "tool_choice": "auto"
  113. #
  114. # 在没有 tools 的情况下存在
  115. # ------------------------------------------------
  116. if not data["tools"]:
  117. data.pop("tools", None)
  118. data.pop("tool_choice", None)
  119. # ----------------------------------------------------
  120. # 没有 tools 时,强制删除 tool_choice
  121. # ----------------------------------------------------
  122. if "tools" not in data:
  123. data.pop("tool_choice", None)
  124. # ====================================================
  125. # 打印最终 Tools
  126. # ====================================================
  127. print("\n===== FINAL TOOLS =====")
  128. if "tools" in data:
  129. for i, tool in enumerate(data["tools"]):
  130. print(
  131. f"[{i}] {tool.get('type')}"
  132. )
  133. else:
  134. print("No tools")
  135. # ====================================================
  136. # 重新生成 JSON
  137. # ====================================================
  138. body = json.dumps(
  139. data,
  140. ensure_ascii=False
  141. ).encode("utf-8")
  142. # ====================================================
  143. # 打印最终请求
  144. # ====================================================
  145. print("\n===== REQUEST =====")
  146. try:
  147. print(
  148. json.dumps(
  149. data,
  150. ensure_ascii=False,
  151. indent=2
  152. )
  153. )
  154. except Exception:
  155. print(body[:2000])
  156. # ====================================================
  157. # 请求 Agnes
  158. # ====================================================
  159. req = urllib.request.Request(
  160. AGNES_URL,
  161. data=body,
  162. method="POST",
  163. headers={
  164. "Authorization": f"Bearer {AGNES_KEY}",
  165. "Content-Type": "application/json",
  166. # Codex 一般会请求 SSE
  167. "Accept": self.headers.get(
  168. "Accept",
  169. "text/event-stream"
  170. ),
  171. },
  172. )
  173. # ====================================================
  174. # 发送到 Agnes
  175. # ====================================================
  176. try:
  177. resp = urllib.request.urlopen(
  178. req,
  179. timeout=600
  180. )
  181. print(
  182. "\nAgnes:",
  183. resp.status
  184. )
  185. # ------------------------------------------------
  186. # 返回 HTTP 状态
  187. # ------------------------------------------------
  188. self.send_response(resp.status)
  189. # ------------------------------------------------
  190. # Content-Type
  191. # ------------------------------------------------
  192. content_type = resp.headers.get(
  193. "Content-Type",
  194. "text/event-stream"
  195. )
  196. self.send_header(
  197. "Content-Type",
  198. content_type
  199. )
  200. # ------------------------------------------------
  201. # SSE / HTTP 相关 Header
  202. # ------------------------------------------------
  203. self.send_header(
  204. "Cache-Control",
  205. "no-cache"
  206. )
  207. self.send_header(
  208. "Connection",
  209. "keep-alive"
  210. )
  211. self.end_headers()
  212. # =================================================
  213. # 流式转发 Agnes SSE
  214. # =================================================
  215. while True:
  216. chunk = resp.read(4096)
  217. if not chunk:
  218. break
  219. self.wfile.write(chunk)
  220. self.wfile.flush()
  221. print("\n===== AGNES STREAM END =====")
  222. # ====================================================
  223. # Agnes HTTP Error
  224. # ====================================================
  225. except urllib.error.HTTPError as e:
  226. error_body = e.read()
  227. print(
  228. "\n===== AGNES ERROR ====="
  229. )
  230. print(
  231. error_body.decode(
  232. "utf-8",
  233. errors="replace"
  234. )
  235. )
  236. try:
  237. self.send_response(e.code)
  238. self.send_header(
  239. "Content-Type",
  240. "application/json"
  241. )
  242. self.send_header(
  243. "Connection",
  244. "close"
  245. )
  246. self.end_headers()
  247. self.wfile.write(
  248. error_body
  249. )
  250. except Exception as send_error:
  251. print(
  252. "ERROR SENDING ERROR RESPONSE:",
  253. repr(send_error)
  254. )
  255. # ====================================================
  256. # 其他异常
  257. # ====================================================
  258. except Exception as e:
  259. print(
  260. "\n===== PROXY ERROR ====="
  261. )
  262. print(
  263. repr(e)
  264. )
  265. try:
  266. self.send_response(500)
  267. self.send_header(
  268. "Content-Type",
  269. "application/json"
  270. )
  271. self.send_header(
  272. "Connection",
  273. "close"
  274. )
  275. self.end_headers()
  276. msg = json.dumps({
  277. "error": {
  278. "message": str(e),
  279. "type": "proxy_error"
  280. }
  281. }).encode("utf-8")
  282. self.wfile.write(msg)
  283. except Exception:
  284. pass
  285. # ========================================================
  286. # 禁止默认日志格式
  287. # ========================================================
  288. def log_message(self, fmt, *args):
  289. print(
  290. fmt % args
  291. )
  292. # ============================================================
  293. # 启动服务器
  294. # ============================================================
  295. server = ThreadingHTTPServer(
  296. ("0.0.0.0", 4000),
  297. Handler
  298. )
  299. print(
  300. "Proxy listening on :4000"
  301. )
  302. print(
  303. "→",
  304. AGNES_URL
  305. )
  306. print(
  307. "Supported tools:",
  308. ", ".join(SUPPORTED_TOOLS)
  309. )
  310. server.serve_forever()
  311. # #!/usr/bin/env python3
  312. # from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
  313. # import urllib.request
  314. # import json
  315. # AGNES_URL = "https://apihub.agnes-ai.com/v1/responses"
  316. # AGNES_KEY = "sk-5bWELiz7grmKat0QTTQ6WJhEF4JWg8gMTPGOb4kcVOCMBrHH"
  317. # class Handler(BaseHTTPRequestHandler):
  318. # protocol_version = "HTTP/1.1"
  319. # def do_POST(self):
  320. # if self.path != "/v1/responses":
  321. # self.send_error(404)
  322. # return
  323. # length = int(self.headers.get("Content-Length", 0))
  324. # body = self.rfile.read(length)
  325. # data = json.loads(body)
  326. # if data.get("model") == "gpt-5.5":
  327. # data["model"] = "agnes-3.0-flash"
  328. # # 2. 删除 Agnes 不支持的 custom tools
  329. # if "tools" in data:
  330. # tools = data["tools"]
  331. # if isinstance(tools, list):
  332. # data["tools"] = [
  333. # tool for tool in tools
  334. # if tool.get("type") != "custom"
  335. # ]
  336. # # 如果已经没有 tools,tool_choice 也必须删除
  337. # if not data["tools"]:
  338. # data.pop("tools", None)
  339. # data.pop("tool_choice", None)
  340. # # 3. 没有 tools 时不能存在 tool_choice
  341. # if "tools" not in data:
  342. # data.pop("tool_choice", None)
  343. # body = json.dumps(
  344. # data,
  345. # ensure_ascii=False
  346. # ).encode("utf-8")
  347. # print("\n===== REQUEST =====")
  348. # try:
  349. # print(json.dumps(json.loads(body), ensure_ascii=False, indent=2))
  350. # except:
  351. # print(body[:1000])
  352. # req = urllib.request.Request(
  353. # AGNES_URL,
  354. # data=body,
  355. # method="POST",
  356. # headers={
  357. # "Authorization": f"Bearer {AGNES_KEY}",
  358. # "Content-Type": "application/json",
  359. # "Accept": self.headers.get(
  360. # "Accept",
  361. # "text/event-stream"
  362. # ),
  363. # },
  364. # )
  365. # try:
  366. # resp = urllib.request.urlopen(req, timeout=600)
  367. # print("Agnes:", resp.status)
  368. # self.send_response(resp.status)
  369. # content_type = resp.headers.get(
  370. # "Content-Type",
  371. # "text/event-stream"
  372. # )
  373. # self.send_header("Content-Type", content_type)
  374. # self.send_header("Cache-Control", "no-cache")
  375. # self.send_header("Connection", "keep-alive")
  376. # self.end_headers()
  377. # while True:
  378. # chunk = resp.read(4096)
  379. # if not chunk:
  380. # break
  381. # self.wfile.write(chunk)
  382. # self.wfile.flush()
  383. # except urllib.error.HTTPError as e:
  384. # error_body = e.read()
  385. # print("\n===== AGNES ERROR =====")
  386. # print(error_body.decode("utf-8", errors="replace"))
  387. # self.send_response(e.code)
  388. # self.send_header("Content-Type", "application/json")
  389. # self.end_headers()
  390. # self.wfile.write(error_body)
  391. # except Exception as e:
  392. # print("ERROR:", repr(e))
  393. # try:
  394. # self.send_response(500)
  395. # self.send_header("Content-Type", "application/json")
  396. # self.end_headers()
  397. # msg = json.dumps({
  398. # "error": str(e)
  399. # }).encode()
  400. # self.wfile.write(msg)
  401. # except:
  402. # pass
  403. # def log_message(self, fmt, *args):
  404. # print(fmt % args)
  405. # server = ThreadingHTTPServer(
  406. # ("0.0.0.0", 9000),
  407. # Handler
  408. # )
  409. # print("Proxy listening on :9000")
  410. # print("→", AGNES_URL)
  411. # server.serve_forever()