feat: route codex patch repair through responses api

This commit is contained in:
thanhnv
2026-07-18 11:53:24 +07:00
parent 4f8a53bda9
commit 61a8253679
5 changed files with 70 additions and 8 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ services:
# gateway; a gateway key must never be assumed valid for api.openai.com. # gateway; a gateway key must never be assumed valid for api.openai.com.
OPENAI_API_KEY: ${OPENAI_API_KEY:-} OPENAI_API_KEY: ${OPENAI_API_KEY:-}
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-} ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
CASAN_GOAL_OPENAI_MODEL: ${CASAN_GOAL_OPENAI_MODEL:-gpt-4.1} CASAN_GOAL_OPENAI_MODEL: ${CASAN_GOAL_OPENAI_MODEL:-gpt-5.3-codex}
CASAN_GOAL_ANTHROPIC_MODEL: ${CASAN_GOAL_ANTHROPIC_MODEL:-claude-3-5-sonnet-latest} CASAN_GOAL_ANTHROPIC_MODEL: ${CASAN_GOAL_ANTHROPIC_MODEL:-claude-3-5-sonnet-latest}
CASAN_PREFLIGHT: ${CASAN_PREFLIGHT:-0} CASAN_PREFLIGHT: ${CASAN_PREFLIGHT:-0}
CASAN_AUTH_BRIDGE_URL: http://host.docker.internal:20130 CASAN_AUTH_BRIDGE_URL: http://host.docker.internal:20130
+1 -1
View File
@@ -10,5 +10,5 @@ CASAN_PREFLIGHT=1
# in governed goal review and H2 patch-format recovery when configured. # in governed goal review and H2 patch-format recovery when configured.
OPENAI_API_KEY= OPENAI_API_KEY=
ANTHROPIC_API_KEY= ANTHROPIC_API_KEY=
CASAN_GOAL_OPENAI_MODEL=gpt-4.1 CASAN_GOAL_OPENAI_MODEL=gpt-5.3-codex
CASAN_GOAL_ANTHROPIC_MODEL=claude-3-5-sonnet-latest CASAN_GOAL_ANTHROPIC_MODEL=claude-3-5-sonnet-latest
@@ -158,7 +158,7 @@ export class GoalsService {
return { model: String(runtime.CASAN_CHAT_SELECTED_MODEL || model), runtime }; return { model: String(runtime.CASAN_CHAT_SELECTED_MODEL || model), runtime };
}); });
const envCloudCandidates = [ const envCloudCandidates = [
process.env.OPENAI_API_KEY ? { model: `openai:${process.env.CASAN_GOAL_OPENAI_MODEL || 'gpt-4.1'}`, runtime: {} } : null, process.env.OPENAI_API_KEY ? { model: `openai:${process.env.CASAN_GOAL_OPENAI_MODEL || 'gpt-5.3-codex'}`, runtime: {} } : null,
process.env.ANTHROPIC_API_KEY ? { model: `anthropic:${process.env.CASAN_GOAL_ANTHROPIC_MODEL || 'claude-3-5-sonnet-latest'}`, runtime: {} } : null, process.env.ANTHROPIC_API_KEY ? { model: `anthropic:${process.env.CASAN_GOAL_ANTHROPIC_MODEL || 'claude-3-5-sonnet-latest'}`, runtime: {} } : null,
].filter((candidate): candidate is { model: string; runtime: Record<string, string> } => candidate !== null); ].filter((candidate): candidate is { model: string; runtime: Record<string, string> } => candidate !== null);
const cloudCandidates = [...envCloudCandidates, ...savedCloudCandidates].filter((candidate, index, rows) => rows.findIndex((row) => row.model === candidate.model) === index); const cloudCandidates = [...envCloudCandidates, ...savedCloudCandidates].filter((candidate, index, rows) => rows.findIndex((row) => row.model === candidate.model) === index);
@@ -239,6 +239,58 @@ def call_openai(model_name, prompt, role):
} }
def parse_responses_payload(payload):
try:
text = str(payload.get("output_text") or "").strip()
if not text:
text = "".join(
str(part.get("text") or "")
for item in payload.get("output", []) if isinstance(item, dict)
for part in item.get("content", []) if isinstance(part, dict) and part.get("type") == "output_text"
).strip()
usage = payload["usage"]
input_tokens = int(usage["input_tokens"])
output_tokens = int(usage["output_tokens"])
if not text:
raise ValueError("output text missing")
except (KeyError, TypeError, ValueError) as exc:
fail(f"provider_usage_invalid openai-responses {type(exc).__name__}: {str(exc)[:80]}")
return text, input_tokens, output_tokens
def call_openai_responses(model_name, prompt, role):
"""Use Responses API for Codex models that are not Chat Completions routes."""
host = "api.openai.com"
if host not in ALLOWED_CLOUD:
fail(f"endpoint_not_allowed openai host={host}")
key = os.environ["OPENAI_API_KEY"]
body = {
"model": model_name,
"input": prompt,
"max_output_tokens": generation_max_tokens(role),
"store": False,
}
data = json.dumps(body).encode()
req = urllib.request.Request(
f"https://{host}/v1/responses", data=data,
headers={"Content-Type": "application/json", "Authorization": f"Bearer {key}"},
)
t0 = time.time()
try:
with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT) as resp:
payload = json.loads(resp.read().decode())
except Exception as exc:
fail(f"backend_unreachable {type(exc).__name__}: {str(exc)[:120]}")
latency_ms = int((time.time() - t0) * 1000)
text, input_tokens, output_tokens = parse_responses_payload(payload)
return {
"text": text,
"input_tokens": input_tokens,
"output_tokens": output_tokens,
"latency_ms": latency_ms,
}
def openai_compatible_url(): def openai_compatible_url():
"""Return a vetted OpenAI-compatible chat-completions endpoint. """Return a vetted OpenAI-compatible chat-completions endpoint.
@@ -412,7 +464,7 @@ def main():
if backend == "ollama": if backend == "ollama":
result = call_ollama(model_name, prompt, args.role) result = call_ollama(model_name, prompt, args.role)
elif backend == "openai": elif backend == "openai":
result = call_openai(model_name, prompt, args.role) result = call_openai_responses(model_name, prompt, args.role) if model_name.endswith("-codex") else call_openai(model_name, prompt, args.role)
elif backend == "openai-compatible": elif backend == "openai-compatible":
result = call_openai_compatible(model_name, prompt, args.role) result = call_openai_compatible(model_name, prompt, args.role)
else: # anthropic else: # anthropic
@@ -152,6 +152,11 @@ class FakeResp:
def fake_urlopen(req, timeout): def fake_urlopen(req, timeout):
seen.append((req.full_url, dict(req.header_items()), json.loads(req.data.decode()))) seen.append((req.full_url, dict(req.header_items()), json.loads(req.data.decode())))
if req.full_url.endswith("/v1/responses"):
return FakeResp({
"output_text": "CASAN_OK",
"usage": {"input_tokens": 13, "output_tokens": 4},
})
if "openai.com" in req.full_url or "127.0.0.1:20128" in req.full_url: if "openai.com" in req.full_url or "127.0.0.1:20128" in req.full_url:
return FakeResp({ return FakeResp({
"choices": [{"message": {"content": "SAFE"}}], "choices": [{"message": {"content": "SAFE"}}],
@@ -166,17 +171,21 @@ def fake_urlopen(req, timeout):
mc.urllib.request.urlopen = fake_urlopen mc.urllib.request.urlopen = fake_urlopen
op = mc.call_openai("gpt-test", "hello", "classify") op = mc.call_openai("gpt-test", "hello", "classify")
codex = mc.call_openai_responses("gpt-5.3-codex", "hello", "generate")
gw = mc.call_openai_compatible("local-test", "hello", "classify") gw = mc.call_openai_compatible("local-test", "hello", "classify")
an = mc.call_anthropic("claude-test", "hello", "judge") an = mc.call_anthropic("claude-test", "hello", "judge")
assert op["text"] == "SAFE" and op["input_tokens"] == 11 and op["output_tokens"] == 3, op assert op["text"] == "SAFE" and op["input_tokens"] == 11 and op["output_tokens"] == 3, op
assert codex["text"] == "CASAN_OK" and codex["input_tokens"] == 13 and codex["output_tokens"] == 4, codex
assert gw["text"] == "SAFE" and gw["input_tokens"] == 11 and gw["output_tokens"] == 3, gw assert gw["text"] == "SAFE" and gw["input_tokens"] == 11 and gw["output_tokens"] == 3, gw
assert an["text"] == "APPROVED" and an["input_tokens"] == 17 and an["output_tokens"] == 5, an assert an["text"] == "APPROVED" and an["input_tokens"] == 17 and an["output_tokens"] == 5, an
assert seen[0][0] == "https://api.openai.com/v1/chat/completions", seen[0] assert seen[0][0] == "https://api.openai.com/v1/chat/completions", seen[0]
assert seen[1][0] == "http://127.0.0.1:20128/v1/chat/completions", seen[1] assert seen[1][0] == "https://api.openai.com/v1/responses", seen[1]
assert seen[2][0] == "https://api.anthropic.com/v1/messages", seen[2] assert seen[2][0] == "http://127.0.0.1:20128/v1/chat/completions", seen[2]
assert seen[3][0] == "https://api.anthropic.com/v1/messages", seen[3]
assert seen[0][2]["temperature"] == 0 and seen[0][2]["max_tokens"] == 16, seen[0][2] assert seen[0][2]["temperature"] == 0 and seen[0][2]["max_tokens"] == 16, seen[0][2]
assert seen[1][2]["temperature"] == 0 and seen[1][2]["max_tokens"] == 16, seen[1][2] assert seen[1][2]["max_output_tokens"] == 1400 and seen[1][2]["store"] is False, seen[1][2]
assert "temperature" not in seen[2][2] and seen[2][2]["max_tokens"] == 16, seen[2][2] assert seen[2][2]["temperature"] == 0 and seen[2][2]["max_tokens"] == 16, seen[2][2]
assert "temperature" not in seen[3][2] and seen[3][2]["max_tokens"] == 16, seen[3][2]
os.environ["CASAN_OPENAI_COMPATIBLE_BASE_URL"] = "http://169.254.169.254/v1" os.environ["CASAN_OPENAI_COMPATIBLE_BASE_URL"] = "http://169.254.169.254/v1"
try: try:
@@ -188,6 +197,7 @@ else:
for fn, bad in ( for fn, bad in (
(mc.parse_openai_payload, {"choices": [{"message": {"content": "SAFE"}}]}), (mc.parse_openai_payload, {"choices": [{"message": {"content": "SAFE"}}]}),
(mc.parse_responses_payload, {"output_text": "CASAN_OK"}),
(mc.parse_anthropic_payload, {"content": [{"type": "text", "text": "APPROVED"}]}), (mc.parse_anthropic_payload, {"content": [{"type": "text", "text": "APPROVED"}]}),
): ):
try: try: