| | #!/usr/bin/env python3 | | | """OpenAI-compatible shim in front of Cognition's Connect-RPC GetChatMessage. | | | | | | Serves POST /v1/chat/completions (stream + non-stream) and GET /v1/models by | | | translating to exa.api_server_pb.ApiServerService/GetChatMessage against | | | server.codeium.com, authenticated with the Devin CLI's stored credential. | | | Runs behind CLIProxyAPI as an openai-compatibility upstream. | | | """ | | | import glob | | | import gzip | | | import json | | | import os | | | import re | | | import struct | | | import threading | | | import time | | | import uuid | | | from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer | | | | | | import requests | | | from google.protobuf import descriptor_pb2, descriptor_pool, message_factory | | | from google.protobuf import timestamp_pb2, duration_pb2, any_pb2, struct_pb2 | | | from google.protobuf import wrappers_pb2, empty_pb2, field_mask_pb2, type_pb2 | | | from google.protobuf import source_context_pb2, api_pb2 | | | | | | HERE = os.path.dirname(os.path.abspath(file)) | | | UPSTREAM = "https://server.codeium.com" | | | AUTH_PATH = "/exa.auth_pb.AuthService/GetUserJwt" | | | CHAT_PATH = "/exa.api_server_pb.ApiServerService/GetChatMessage" | | | IDE_NAME = "windsurf" | | | IDE_VERSION = "3.51.3" | | | EXT_VERSION = "1.48.2" |
| | SESSION_PREFIX = "devin-session-token$" |
| | STOP_PATTERNS = ["<\|user\|>", "<\|bot\|>", "<\|context_request\|>", "<\|endoftext\|>", "<\|end_of_turn\|>"] |
| | |
| | MODELS = [ |
| | "swe-2-max", "swe-2-high", "swe-2-medium", |
| | "swe-1-7", "swe-1-7-medium", "swe-1-7-lightning", "swe-1-7-lightning-medium", |
| | "swe-1-6", "swe-1-6-fast", |
| | ] | | | | | | SRC_USER, SRC_SYSTEM, SRC_TOOL = 1, 2, 4 | | | REQ_CASCADE, PLANNER_DEFAULT = 5, 1 | | | CACHE_EPHEMERAL = 1 | | | STOP_MAX_TOKENS = 3 | | | | | | pool = descriptor_pool.DescriptorPool() | | | for _m in (timestamp_pb2, duration_pb2, any_pb2, struct_pb2, descriptor_pb2, | | | wrappers_pb2, empty_pb2, field_mask_pb2, type_pb2, source_context_pb2, api_pb2): | | | _fd = descriptor_pb2.FileDescriptorProto() | | | _fd.ParseFromString(_m.DESCRIPTOR.serialized_pb) | | | try: | | | pool.Add(_fd) | | | except Exception: | | | pass |
| | _fdps = {} |
| | for _p in glob.glob(os.path.join(HERE, "*.fdp")): |
| | _fd = descriptor_pb2.FileDescriptorProto() |
| | _fd.ParseFromString(open(_p, "rb").read()) |
| | _fdps[_fd.name] = _fd |
| | _added = set() |
| | for _ in range(40): |
| | for _name, _fd in _fdps.items(): |
| | if _name in _added: | | | continue | | | try: |
| | pool.Add(_fd) |
| | _added.add(_name) |
| | except Exception: | | | pass | | | | | | | | | def _msg(name): | | | return message_factory.GetMessageClass(pool.FindMessageTypeByName(name)) | | | | | | | | | GetUserJwtRequest = _msg("exa.auth_pb.GetUserJwtRequest") | | | GetUserJwtResponse = _msg("exa.auth_pb.GetUserJwtResponse") | | | GetChatMessageRequest = _msg("exa.api_server_pb.GetChatMessageRequest") | | | GetChatMessageResponse = _msg("exa.api_server_pb.GetChatMessageResponse") | | | | | | |
| | def _load_key(): |
| | if os.environ.get("DEVIN_SHIM_API_KEY"): |
| | return os.environ["DEVIN_SHIM_API_KEY"] |
| | # Prefer the shim's own login (created via XDG_DATA_HOME=~/devin-shim/data | | | # devin auth login); fall back to the Devin CLI's credential. |
| | for cred in (os.path.expanduser("~/devin-shim/data/devin/credentials.toml"), |
| | os.path.expanduser("~/.local/share/devin/credentials.toml")): |
| | if not os.path.exists(cred): |
| | continue |
| | for line in open(cred): |
| | if line.startswith("windsurf_api_key"): |
| | return line.split('"')[1] |
| | raise RuntimeError("no devin credential found") | | | | | | |
| | API_KEY = _load_key() |
| | if not API_KEY.startswith(SESSION_PREFIX): |
| | API_KEY = SESSION_PREFIX + API_KEY | | | |
| | _jwt_lock = threading.Lock() |
| | _jwt = {"token": None, "exp": 0.0, "base": None} |
| | | | | | | | def _metadata(jwt=""): | | | return { | | | "api_key": API_KEY, | | | "user_jwt": jwt, | | | "ide_name": IDE_NAME, | | | "ide_version": IDE_VERSION, | | | "extension_name": "windsurf", | | | "extension_version": EXT_VERSION, | | | "locale": "en", |
| | "session_id": str(uuid.uuid4()), |
| | "request_id": uuid.uuid4().int & (2**63 - 1), |
| | } | | | | | | | | | def _jwt_expiry(token): | | | try: | | | import base64 |
| | payload = token.split(".")[1] |
| | payload += "=" * (-len(payload) % 4) |
| | return json.loads(base64.urlsafe_b64decode(payload)).get("exp", 0) |
| | except Exception: | | | return 0.0 | | | | | | | | | def get_jwt(force=False): | | | with _jwt_lock: |
| | now = time.time() |
| | if not force and _jwt["token"] and _jwt["exp"] - 60 > now: |
| | return _jwt["token"], _jwt["base"] |
| | req = GetUserJwtRequest(metadata=_metadata()) |
| | r = requests.post(UPSTREAM + AUTH_PATH, data=req.SerializeToString(), |
| | headers={"content-type": "application/proto", |
| | "connect-protocol-version": "1"}, timeout=30) |
| | r.raise_for_status() |
| | resp = GetUserJwtResponse() |
| | try: | | | resp.ParseFromString(r.content) | | | except Exception: | | | resp.ParseFromString(gzip.decompress(r.content)) | | | if not resp.user_jwt: | | | raise RuntimeError("GetUserJwt returned empty jwt") |
| | _jwt["token"] = resp.user_jwt |
| | _jwt["exp"] = _jwt_expiry(resp.user_jwt) or now + 3300 |
| | _jwt["base"] = resp.custom_api_server_url.strip() or None |
| | return _jwt["token"], _jwt["base"] |
| | | | | |
| | def _text_of(content): |
| | if isinstance(content, str): |
| | return content |
| | out = [] |
| | for part in content or []: |
| | if part.get("type") == "text": |
| | out.append(part.get("text", "")) |
| | return "".join(out) |
| | | | | | | | # Cognition's input classifier rejects these Claude Code system-prompt blocks | | | # (competitor identity, security-policy vocabulary, product marketing + URLs). | | | # Rewrite them to neutral equivalents; behavior instructions are unchanged. |
| | _SYS_REWRITES = [ |
| | (re.compile(r"You are (?:a )?Claude[^.]*\."), "You are a coding agent."), |
| | (re.compile(r"IMPORTANT: Assist with authorized security testing.*?(?=\n\s*\n\|\Z)", re.S), |
| | "Assist with authorized security testing and defensive security work; refuse harmful or destructive requests."), | | | (re.compile(r"\n?\s*-?\sClaude Code is available[^\n]"), ""), | | | (re.compile(r"- For clear communication with the user the assistant MUST avoid using emojis."), | | | "- For clear communication with the user, avoid emojis."), | | | ] | | | | | | | | | def _scrub_system(text): | | | for rx, rep in _SYS_REWRITES: | | | text = rx.sub(rep, text) | | | return text | | | | | | | | | # TaskOutput's shipped description trips the same classifier in combination | | | # (per-line fragments pass). It is deprecated upstream; send a short equivalent. | | | _TOOL_DESC_REWRITES = { | | | "TaskOutput": "Get the output of a running or completed background task (shell, agent, or remote session) by task_id.", | | | } | | | | | | |
| | def _images_of(content): |
| | if isinstance(content, str) or not content: |
| | return [] |
| | out = [] |
| | for part in content or []: |
| | if part.get("type") == "image_url": |
| | url = part.get("image_url", {}).get("url", "") |
| | if url.startswith("data:"): |
| | mime, _, b64 = url[5:].partition(";base64,") |
| | out.append({"base64_data": b64, "mime_type": mime or "image/png"}) |
| | return out | | | | | | |
| | def build_request(body): |
| | system_parts, prompts = [], [] |
| | cascade_id = str(uuid.uuid4()) |
| | for i, m in enumerate(body.get("messages", [])): |
| | role = m.get("role") |
| | mid = str(uuid.uuid5(uuid.NAMESPACE_URL, f"{cascade_id}\0{i}\0{role}")) |
| | if role in ("system", "developer"): |
| | system_parts.append(_scrub_system(_text_of(m.get("content")))) |
| | elif role == "user": |
| | prompts.append({"message_id": mid, "source": SRC_USER, |
| | "prompt": _text_of(m.get("content")), |
| | "images": _images_of(m.get("content"))}) |
| | elif role == "assistant": |
| | tcs = [{"id": tc.get("id", ""), "name": tc.get("function", {}).get("name", ""), |
| | "arguments_json": tc.get("function", {}).get("arguments", "")} |
| | for tc in m.get("tool_calls") or []] |
| | text = _text_of(m.get("content")) |
| | thinking = m.get("reasoning_content") or "" |
| | prompts.append({"message_id": mid, "source": SRC_SYSTEM, "prompt": text, |
| | "thinking": thinking, "tool_calls": tcs}) |
| | elif role == "tool": |
| | prompts.append({"message_id": mid, "source": SRC_TOOL, |
| | "tool_call_id": m.get("tool_call_id", ""), |
| | "prompt": _text_of(m.get("content")), |
| | "images": _images_of(m.get("content"))}) |
| | tools = [{"name": t["function"]["name"], |
| | "description": _TOOL_DESC_REWRITES.get(t["function"]["name"], |
| | t["function"].get("description", "")), |
| | "json_schema_string": json.dumps(t["function"].get("parameters") or {}), |
| | "strict": bool(t["function"].get("strict"))} |
| | for t in body.get("tools") or [] if t.get("type") == "function"] |
| | tc = body.get("tool_choice") |
| | tool_choice = {"option_name": "auto"} |
| | if isinstance(tc, str) and tc in ("auto", "required", "none"): |
| | tool_choice = {"option_name": tc} |
| | elif isinstance(tc, dict): |
| | fn = (tc.get("function") or {}).get("name") |
| | if fn: |
| | tool_choice = {"tool_name": fn} |
| | stops = list(STOP_PATTERNS) |
| | stop = body.get("stop") |
| | stops += [stop] if isinstance(stop, str) else list(stop or []) |
| | conf = {"num_completions": 1, "max_newlines": 200, "top_k": 50, |
| | "stop_patterns": stops, "fim_eot_prob_threshold": 1} |
| | conf["max_tokens"] = int(body.get("max_completion_tokens") or body.get("max_tokens") or 64000) |
| | if body.get("temperature") is not None: |
| | conf["temperature"] = conf["first_temperature"] = float(body["temperature"]) |
| | else: |
| | conf["temperature"] = conf["first_temperature"] = 0.4 |
| | if body.get("top_p") is not None: |
| | conf["top_p"] = float(body["top_p"]) |
| | else: |
| | conf["top_p"] = 1 |
| | model = body.get("model", "swe-2-max") |
| | if "/" in model: | | | model = model.rsplit("/", 1)[-1] | | | return GetChatMessageRequest( |
| | metadata=_metadata(get_jwt()[0]), |
| | prompt="\n\n".join(p for p in system_parts if p), |
| | chat_message_prompts=prompts, | | | chat_model_uid=model, | | | request_type=REQ_CASCADE, | | | planner_mode=PLANNER_DEFAULT, | | | tool_choice=tool_choice, | | | system_prompt_cache_options={"type": CACHE_EPHEMERAL}, | | | disable_parallel_tool_calls=False, | | | cascade_id=cascade_id, | | | execution_id=str(uuid.uuid4()), | | | configuration=conf, | | | tools=tools, | | | ), model | | | | | | | | | def chat_stream(req): | | | """Yield (GetChatMessageResponse, None) per frame or (None, error_str) on trailer error.""" | | | jwt, base = get_jwt() | | | req.metadata.user_jwt = jwt |
| | body = req.SerializeToString() |
| | for attempt in range(2): |
| | gz = gzip.compress(body) |
| | frame = bytes([1]) + struct.pack(">I", len(gz)) + gz |
| | r = requests.post((base or UPSTREAM) + CHAT_PATH, data=frame, |
| | headers={"content-type": "application/connect+proto", |
| | "connect-protocol-version": "1", |
| | "connect-content-encoding": "gzip", |
| | "connect-accept-encoding": "gzip", |
| | "user-agent": "connect-go/1.18.1 (go1.26.3)"}, |
| | timeout=600, stream=True) |
| | if r.status_code == 200: |
| | break |
| | if r.status_code in (401, 403) and attempt == 0: |
| | jwt, base = get_jwt(force=True) |
| | req.metadata.user_jwt = jwt | | | body = req.SerializeToString() | | | continue | | | yield None, f"upstream {r.status_code}: {r.text[:400]}" | | | return | | | buf = b"" | | | for chunk in r.iter_content(65536): | | | buf += chunk |
| | while len(buf) >= 5: |
| | flag = buf[0] |
| | ln = struct.unpack(">I", buf[1:5])[0] |
| | if len(buf) < 5 + ln: |
| | break |
| | payload = buf[5:5 + ln] |
| | buf = buf[5 + ln:] |
| | if flag & 2: | | | trailer = gzip.decompress(payload) if flag & 1 else payload | | | try: | | | err = json.loads(trailer).get("error") or {} | | | except Exception: |
| | err = {} |
| | if err.get("message"): |
| | yield None, f"{err.get('code', 'error')}: {err['message']}" |
| | continue | | | raw = gzip.decompress(payload) if flag & 1 else payload |
| | msg = GetChatMessageResponse() |
| | msg.ParseFromString(raw) |
| | yield msg, None | | | | | | |
| | def openai_chunk(model, delta=None, finish=None, usage=None): |
| | ch = {"index": 0} |
| | if delta is not None: | | | ch["delta"] = delta | | | if finish: |
| | ch["finish_reason"] = finish |
| | out = {"id": "chatcmpl-devin", "object": "chat.completion.chunk", |
| | "created": int(time.time()), "model": model, "choices": [ch]} |
| | if usage: | | | out["usage"] = usage | | | return out | | | | | | |
| | def sse(obj): |
| | return f"data: {json.dumps(obj)}\n\n".encode() |
| | | | | | | | def run_chat(body, wfile): | | | """Translate one OpenAI chat.completions call; returns (final_msg_dict, usage, error).""" | | | try: | | | req, model = build_request(body) | | | except Exception as e: |
| | return None, None, f"request build: {e}" |
| | stream = bool(body.get("stream")) |
| | w = wfile if stream else None | | | headers_sent = False | | | # Cognition's input classifier denies borderline payloads nondeterministically | | | # (same body observed pass/fail). Retry while nothing reached the client. | | | for attempt in range(3): | | | if attempt: | | | req, model = build_request(body) | | | if w and not headers_sent: |
| | w.write(b"HTTP/1.1 200 OK\r\ncontent-type: text/event-stream\r\n" |
| | b"cache-control: no-cache\r\nconnection: close\r\n\r\n") |
| | w.flush() |
| | w.write(sse(openai_chunk(model, delta={"role": "assistant"}))) |
| | w.flush() |
| | headers_sent = True |
| | text, thinking = [], [] |
| | tool_blocks = {} # id -> {"name":..., "json":...} |
| | tool_order = [] |
| | usage = {} |
| | stop = 0 | | | err = None | | | emitted = False | | | for msg, e in chat_stream(req): | | | if e: | | | err = e | | | break | | | if msg.delta_text: | | | text.append(msg.delta_text) | | | if w: | | | emitted = True |
| | w.write(sse(openai_chunk(model, delta={"content": msg.delta_text}))) |
| | w.flush() |
| | if msg.delta_thinking: | | | thinking.append(msg.delta_thinking) | | | if w: | | | emitted = True |
| | w.write(sse(openai_chunk(model, delta={"reasoning_content": msg.delta_thinking}))) |
| | w.flush() |
| | for tc in msg.delta_tool_calls: | | | tid = tc.id or (tool_order[-1] if tool_order else "") | | | if not tid: | | | continue | | | if tid not in tool_blocks: |
| | tool_blocks[tid] = {"name": tc.name, "json": ""} |
| | tool_order.append(tid) |
| | if w: | | | emitted = True |
| | idx = tool_order.index(tid) |
| | w.write(sse(openai_chunk(model, delta={"tool_calls": [ |
| | {"index": idx, "id": tid, "type": "function", |
| | "function": {"name": tc.name, "arguments": ""}}]}))) |
| | w.flush() |
| | if tc.name: | | | tool_blocks[tid]["name"] = tc.name | | | if tc.arguments_json: | | | prev = tool_blocks[tid]["json"] | | | acc = tc.arguments_json if tc.arguments_json.startswith(prev) else prev + tc.arguments_json |
| | delta = acc[len(prev):] |
| | tool_blocks[tid]["json"] = acc |
| | if w and delta: | | | emitted = True |
| | idx = tool_order.index(tid) |
| | w.write(sse(openai_chunk(model, delta={"tool_calls": [ |
| | {"index": idx, "function": {"arguments": delta}}]}))) |
| | w.flush() |
| | if msg.usage.input_tokens: |
| | usage = {"prompt_tokens": int(msg.usage.input_tokens), |
| | "completion_tokens": int(msg.usage.output_tokens), |
| | "total_tokens": int(msg.usage.input_tokens + msg.usage.output_tokens)} |
| | stop = msg.stop_reason | | | if err and not emitted and attempt < 2 and "permission_denied" in err: | | | print(f"upstream permission_denied, retrying (attempt {attempt + 2}/3)", flush=True) | | | continue | | | break | | | if err: | | | if w: |
| | w.write(sse({"error": {"message": err, "type": "upstream_error"}})) |
| | w.write(b"data: [DONE]\n\n") |
| | w.flush() |
| | return None, None, err | | | finish = "tool_calls" if tool_blocks else "stop" | | | if not tool_blocks and stop == STOP_MAX_TOKENS: | | | finish = "length" | | | if w: |
| | final = openai_chunk(model, delta={}, finish=finish, |
| | usage=usage if (body.get("stream_options") or {}).get("include_usage") else None) |
| | w.write(sse(final)) |
| | w.write(b"data: [DONE]\n\n") |
| | w.flush() |
| | return None, None, None | | | msg_out = {"role": "assistant", "content": "".join(text)} | | | if thinking: | | | msg_out["reasoning_content"] = "".join(thinking) | | | if tool_blocks: |
| | msg_out["tool_calls"] = [ |
| | {"id": tid, "type": "function", |
| | "function": {"name": b["name"], "arguments": b["json"]}} |
| | for tid, b in ((tid, tool_blocks[tid]) for tid in tool_order)] |
| | msg_out["content"] = msg_out["content"] or None |
| | resp = {"id": "chatcmpl-devin", "object": "chat.completion", |
| | "created": int(time.time()), "model": model, |
| | "choices": [{"index": 0, "message": msg_out, "finish_reason": finish}]} |
| | if usage: | | | resp["usage"] = usage | | | return resp, usage, None | | | | | | | | | class Handler(BaseHTTPRequestHandler): | | | protocol_version = "HTTP/1.1" | | | |
| | def log_message(self, fmt, *args): |
| | print(f"{self.address_string()} {fmt % args}", flush=True) |
| | |
| | def _json(self, code, obj): |
| | data = json.dumps(obj).encode() |
| | self.send_response(code) |
| | self.send_header("content-type", "application/json") |
| | self.send_header("content-length", str(len(data))) |
| | self.end_headers() |
| | self.wfile.write(data) |
| | |
| | def do_GET(self): |
| | if self.path.split("?")[0] in ("/v1/models", "/models"): |
| | self._json(200, {"object": "list", "data": [ |
| | {"id": m, "object": "model", "created": 0, "owned_by": "devin"} for m in MODELS]}) |
| | else: | | | self._json(404, {"error": {"message": "not found", "type": "invalid_request_error"}}) | | | |
| | def do_POST(self): |
| | path = self.path.split("?")[0] |
| | if path not in ("/v1/chat/completions", "/chat/completions"): | | | self._json(404, {"error": {"message": "not found", "type": "invalid_request_error"}}) | | | return | | | try: |
| | raw = self.rfile.read(int(self.headers.get("content-length", 0))) |
| | if os.environ.get("DEVIN_SHIM_DUMP"): |
| | with open(os.environ["DEVIN_SHIM_DUMP"] + f".{time.time_ns()}.json", "wb") as fh: |
| | fh.write(raw) |
| | body = json.loads(raw) |
| | except Exception as e: | | | self._json(400, {"error": {"message": f"bad json: {e}", "type": "invalid_request_error"}}) | | | return | | | if body.get("stream"): | | | # run_chat owns the raw socket from here | | | resp, _, err = run_chat(body, self.wfile) | | | if err: | | | return | | | return | | | resp, _, err = run_chat(body, None) | | | if err: | | | self._json(502, {"error": {"message": err, "type": "upstream_error"}}) | | | else: | | | self._json(200, resp) | | | | | | |
| | if __name__ == "__main__": |
| | port = int(os.environ.get("DEVIN_SHIM_PORT", "8321")) |
| | print(f"devin-shim listening on 127.0.0.1:{port}", flush=True) |
| | ThreadingHTTPServer(("127.0.0.1", port), Handler).serve_forever() |