| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577 |
- #!/usr/bin/env python3
- from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
- import urllib.request
- import urllib.error
- import json
- # ============================================================
- # Agnes 配置
- # ============================================================
- AGNES_URL = "https://apihub.agnes-ai.com/v1/responses"
- AGNES_KEY = "sk-***"
- # ============================================================
- # Agnes 支持的 Tool 类型
- # ============================================================
- # SUPPORTED_TOOLS = {
- # "function",
- # "web_search_preview",
- # "code_interpreter",
- # "mcp",
- # }
- SUPPORTED_TOOLS = {
- "function",
- "web_search_preview",
- # "web_search",
- "code_interpreter",
- "file_search",
- "computer_use",
- "mcp",
- # "tool_search",
- "namespace",
- }
- # ============================================================
- # HTTP Handler
- # ============================================================
- class Handler(BaseHTTPRequestHandler):
- protocol_version = "HTTP/1.1"
- def do_POST(self):
- # ----------------------------------------------------
- # 只处理 /v1/responses
- # ----------------------------------------------------
- if self.path != "/v1/responses":
- self.send_error(404)
- return
- # ----------------------------------------------------
- # 读取请求
- # ----------------------------------------------------
- try:
- length = int(self.headers.get("Content-Length", 0))
- body = self.rfile.read(length)
- data = json.loads(body)
- except Exception as e:
- print("\n===== REQUEST PARSE ERROR =====")
- print(repr(e))
- self.send_response(400)
- self.send_header("Content-Type", "application/json")
- self.send_header("Connection", "close")
- self.end_headers()
- error = json.dumps({
- "error": {
- "message": f"Invalid JSON request: {e}",
- "type": "invalid_request_error"
- }
- }).encode("utf-8")
- self.wfile.write(error)
- return
- # ====================================================
- # 1. Model 映射
- # ====================================================
- original_model = data.get("model")
- if original_model == "gpt-5.5":
- data["model"] = "agnes-3.0-flash"
- print("\n===== MODEL =====")
- print("Codex :", original_model)
- print("Agnes :", data.get("model"))
- # ====================================================
- # 2. Tool 过滤
- #
- # Agnes 当前支持:
- #
- # function
- # web_search_preview
- # code_interpreter
- # mcp
- #
- # Codex 可能发送:
- #
- # custom
- # tool_search
- # 以及其他内部工具
- #
- # 对不支持的类型直接删除
- # ====================================================
- if isinstance(data.get("tools"), list):
- old_tools = data["tools"]
- new_tools = []
- print("\n===== TOOLS =====")
- for i, tool in enumerate(old_tools):
- tool_type = tool.get("type")
- if tool_type in SUPPORTED_TOOLS:
- new_tools.append(tool)
- print(
- f"[{i}] {tool_type:<25} KEEP"
- )
- else:
- print(
- f"[{i}] {str(tool_type):<25} DROP"
- )
- data["tools"] = new_tools
- # ------------------------------------------------
- # 如果已经没有任何 tools
- # Agnes 不允许:
- #
- # "tool_choice": "auto"
- #
- # 在没有 tools 的情况下存在
- # ------------------------------------------------
- if not data["tools"]:
- data.pop("tools", None)
- data.pop("tool_choice", None)
- # ----------------------------------------------------
- # 没有 tools 时,强制删除 tool_choice
- # ----------------------------------------------------
- if "tools" not in data:
- data.pop("tool_choice", None)
- # ====================================================
- # 打印最终 Tools
- # ====================================================
- print("\n===== FINAL TOOLS =====")
- if "tools" in data:
- for i, tool in enumerate(data["tools"]):
- print(
- f"[{i}] {tool.get('type')}"
- )
- else:
- print("No tools")
- # ====================================================
- # 重新生成 JSON
- # ====================================================
- body = json.dumps(
- data,
- ensure_ascii=False
- ).encode("utf-8")
- # ====================================================
- # 打印最终请求
- # ====================================================
- print("\n===== REQUEST =====")
- try:
- print(
- json.dumps(
- data,
- ensure_ascii=False,
- indent=2
- )
- )
- except Exception:
- print(body[:2000])
- # ====================================================
- # 请求 Agnes
- # ====================================================
- req = urllib.request.Request(
- AGNES_URL,
- data=body,
- method="POST",
- headers={
- "Authorization": f"Bearer {AGNES_KEY}",
- "Content-Type": "application/json",
- # Codex 一般会请求 SSE
- "Accept": self.headers.get(
- "Accept",
- "text/event-stream"
- ),
- },
- )
- # ====================================================
- # 发送到 Agnes
- # ====================================================
- try:
- resp = urllib.request.urlopen(
- req,
- timeout=600
- )
- print(
- "\nAgnes:",
- resp.status
- )
- # ------------------------------------------------
- # 返回 HTTP 状态
- # ------------------------------------------------
- self.send_response(resp.status)
- # ------------------------------------------------
- # Content-Type
- # ------------------------------------------------
- content_type = resp.headers.get(
- "Content-Type",
- "text/event-stream"
- )
- self.send_header(
- "Content-Type",
- content_type
- )
- # ------------------------------------------------
- # SSE / HTTP 相关 Header
- # ------------------------------------------------
- self.send_header(
- "Cache-Control",
- "no-cache"
- )
- self.send_header(
- "Connection",
- "keep-alive"
- )
- self.end_headers()
- # =================================================
- # 流式转发 Agnes SSE
- # =================================================
- while True:
- chunk = resp.read(4096)
- if not chunk:
- break
- self.wfile.write(chunk)
- self.wfile.flush()
- print("\n===== AGNES STREAM END =====")
- # ====================================================
- # Agnes HTTP Error
- # ====================================================
- except urllib.error.HTTPError as e:
- error_body = e.read()
- print(
- "\n===== AGNES ERROR ====="
- )
- print(
- error_body.decode(
- "utf-8",
- errors="replace"
- )
- )
- try:
- self.send_response(e.code)
- self.send_header(
- "Content-Type",
- "application/json"
- )
- self.send_header(
- "Connection",
- "close"
- )
- self.end_headers()
- self.wfile.write(
- error_body
- )
- except Exception as send_error:
- print(
- "ERROR SENDING ERROR RESPONSE:",
- repr(send_error)
- )
- # ====================================================
- # 其他异常
- # ====================================================
- except Exception as e:
- print(
- "\n===== PROXY ERROR ====="
- )
- print(
- repr(e)
- )
- try:
- self.send_response(500)
- self.send_header(
- "Content-Type",
- "application/json"
- )
- self.send_header(
- "Connection",
- "close"
- )
- self.end_headers()
- msg = json.dumps({
- "error": {
- "message": str(e),
- "type": "proxy_error"
- }
- }).encode("utf-8")
- self.wfile.write(msg)
- except Exception:
- pass
- # ========================================================
- # 禁止默认日志格式
- # ========================================================
- def log_message(self, fmt, *args):
- print(
- fmt % args
- )
- # ============================================================
- # 启动服务器
- # ============================================================
- server = ThreadingHTTPServer(
- ("0.0.0.0", 4000),
- Handler
- )
- print(
- "Proxy listening on :4000"
- )
- print(
- "→",
- AGNES_URL
- )
- print(
- "Supported tools:",
- ", ".join(SUPPORTED_TOOLS)
- )
- server.serve_forever()
- # #!/usr/bin/env python3
- # from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
- # import urllib.request
- # import json
- # AGNES_URL = "https://apihub.agnes-ai.com/v1/responses"
- # AGNES_KEY = "sk-5bWELiz7grmKat0QTTQ6WJhEF4JWg8gMTPGOb4kcVOCMBrHH"
- # class Handler(BaseHTTPRequestHandler):
- # protocol_version = "HTTP/1.1"
- # def do_POST(self):
- # if self.path != "/v1/responses":
- # self.send_error(404)
- # return
- # length = int(self.headers.get("Content-Length", 0))
- # body = self.rfile.read(length)
- # data = json.loads(body)
- # if data.get("model") == "gpt-5.5":
- # data["model"] = "agnes-3.0-flash"
- # # 2. 删除 Agnes 不支持的 custom tools
- # if "tools" in data:
- # tools = data["tools"]
- # if isinstance(tools, list):
- # data["tools"] = [
- # tool for tool in tools
- # if tool.get("type") != "custom"
- # ]
- # # 如果已经没有 tools,tool_choice 也必须删除
- # if not data["tools"]:
- # data.pop("tools", None)
- # data.pop("tool_choice", None)
- # # 3. 没有 tools 时不能存在 tool_choice
- # if "tools" not in data:
- # data.pop("tool_choice", None)
- # body = json.dumps(
- # data,
- # ensure_ascii=False
- # ).encode("utf-8")
- # print("\n===== REQUEST =====")
- # try:
- # print(json.dumps(json.loads(body), ensure_ascii=False, indent=2))
- # except:
- # print(body[:1000])
- # req = urllib.request.Request(
- # AGNES_URL,
- # data=body,
- # method="POST",
- # headers={
- # "Authorization": f"Bearer {AGNES_KEY}",
- # "Content-Type": "application/json",
- # "Accept": self.headers.get(
- # "Accept",
- # "text/event-stream"
- # ),
- # },
- # )
- # try:
- # resp = urllib.request.urlopen(req, timeout=600)
- # print("Agnes:", resp.status)
- # self.send_response(resp.status)
- # content_type = resp.headers.get(
- # "Content-Type",
- # "text/event-stream"
- # )
- # self.send_header("Content-Type", content_type)
- # self.send_header("Cache-Control", "no-cache")
- # self.send_header("Connection", "keep-alive")
- # self.end_headers()
- # while True:
- # chunk = resp.read(4096)
- # if not chunk:
- # break
- # self.wfile.write(chunk)
- # self.wfile.flush()
- # except urllib.error.HTTPError as e:
- # error_body = e.read()
- # print("\n===== AGNES ERROR =====")
- # print(error_body.decode("utf-8", errors="replace"))
- # self.send_response(e.code)
- # self.send_header("Content-Type", "application/json")
- # self.end_headers()
- # self.wfile.write(error_body)
- # except Exception as e:
- # print("ERROR:", repr(e))
- # try:
- # self.send_response(500)
- # self.send_header("Content-Type", "application/json")
- # self.end_headers()
- # msg = json.dumps({
- # "error": str(e)
- # }).encode()
- # self.wfile.write(msg)
- # except:
- # pass
- # def log_message(self, fmt, *args):
- # print(fmt % args)
- # server = ThreadingHTTPServer(
- # ("0.0.0.0", 9000),
- # Handler
- # )
- # print("Proxy listening on :9000")
- # print("→", AGNES_URL)
- # server.serve_forever()
|