File size: 2,808 Bytes
0b2c941 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | """Which wire dialect is this request written in?
Four dialects reach the intercept, because coding agents did not agree on one:
openai_chat opencode, qwen-coder, goose, swe-agent, mini-swe-agent, terminus-2, ...
openai_responses codex, trae-agent
anthropic claude-code
google gemini-cli, antigravity-sdk
Detection is by path first, then headers, then body shape — strongest signal to weakest. Path is
unambiguous when present; a header is a deliberate client declaration; body shape is a last resort
and can be coincidental, so it is only consulted when nothing better exists.
Getting this wrong is not subtle: a Google request parsed as chat-completions produces a 400 and the
agent silently does nothing, which reads as "captured nothing" rather than as a routing bug. That is
why trae-agent looked like a chat harness for a full night — its config says `provider: openai`, but
the access log showed exactly one `POST /v1/responses` against 465 chat calls.
"""
from __future__ import annotations
from enum import Enum
from typing import Any
class APIType(str, Enum):
ANTHROPIC = "anthropic"
OPENAI_CHAT = "openai_chat"
OPENAI_RESPONSES = "openai_responses"
GOOGLE = "google"
def detect(path: str, headers: dict[str, str], body: dict[str, Any]) -> APIType:
"""Classify one request. Defaults to chat-completions, the most common dialect."""
if "/v1/messages" in path:
return APIType.ANTHROPIC
if "/v1/chat/completions" in path:
return APIType.OPENAI_CHAT
if "/v1/responses" in path:
return APIType.OPENAI_RESPONSES
# Google puts the method in the path: `/v1beta/models/{model}:generateContent`, and its
# streaming variant `:streamGenerateContent`. The comparison must be case-insensitive: the
# streaming form capitalises the G, so a literal `"generateContent" in path` matches the
# non-streaming route and misses every streaming one. A missed Google request is then handed to
# the chat-completions transformer, which finds no `messages` and produces a valid-looking
# response in the wrong envelope, and gemini-cli reports that as nothing at all.
if "generatecontent" in path.lower():
return APIType.GOOGLE
if "anthropic-version" in {k.lower() for k in headers}:
return APIType.ANTHROPIC
if "contents" in body:
return APIType.GOOGLE
if "input" in body and "instructions" in body:
return APIType.OPENAI_RESPONSES
return APIType.OPENAI_CHAT
def extract_model(api_type: APIType, body: dict[str, Any]) -> str:
"""The model name the client asked for, whatever the dialect calls it."""
if api_type is APIType.GOOGLE:
return body.get("model", "gemini-pro")
return body.get("model", "unknown")
|