hermes: normalize Switchyard Codex inputs

This commit is contained in:
jenkins 2026-08-11 22:51:01 -03:00
parent ac61725dee
commit a7feed5dba
3 changed files with 55 additions and 4 deletions

View File

@ -25,7 +25,7 @@ spec:
ai.bstein.dev/execution: Hermes Kanban with durable direct Codex and Claude Code CLI workers ai.bstein.dev/execution: Hermes Kanban with durable direct Codex and Claude Code CLI workers
ai.bstein.dev/model-policy: Jetson-assisted AUTO routing, low through xhigh, cross-provider fallback ai.bstein.dev/model-policy: Jetson-assisted AUTO routing, low through xhigh, cross-provider fallback
ai.bstein.dev/placement: rpi5 preferred; Jetson deferred until state storage is available ai.bstein.dev/placement: rpi5 preferred; Jetson deferred until state storage is available
ai.bstein.dev/config-rev: "20260811-image-routing" ai.bstein.dev/config-rev: "20260811-switchyard-responses-input"
vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: hermes-agent vault.hashicorp.com/role: hermes-agent
vault.hashicorp.com/agent-inject-secret-anthropic-token: kv/data/atlas/hermes/agent-tokens vault.hashicorp.com/agent-inject-secret-anthropic-token: kv/data/atlas/hermes/agent-tokens

View File

@ -105,6 +105,25 @@ def _validate_payload(payload: Any) -> dict[str, Any]:
if not model.startswith("gpt-"): if not model.startswith("gpt-"):
raise ValueError("unsupported Codex model") raise ValueError("unsupported Codex model")
payload["model"] = model payload["model"] = model
response_input = payload.get("input")
if isinstance(response_input, str):
if not response_input.strip():
raise ValueError("non-empty Responses input required")
# Switchyard accepts OpenAI Chat Completions requests and translates
# their final text to a scalar Responses input. The first-party Codex
# endpoint is stricter than the public Responses API and only accepts
# a list of typed input items.
payload["input"] = [
{
"type": "message",
"role": "user",
"content": [{"type": "input_text", "text": response_input}],
}
]
elif isinstance(response_input, dict):
payload["input"] = [response_input]
elif not isinstance(response_input, list) or not response_input:
raise ValueError("non-empty Responses input list required")
# Tenant conversations must not enter the owner's server-side history. # Tenant conversations must not enter the owner's server-side history.
payload["store"] = False payload["store"] = False
payload["stream"] = True payload["stream"] = True

View File

@ -499,16 +499,48 @@ def test_codex_broker_auth_and_request_contract(tmp_path: Path, monkeypatch):
assert module._authorized("Bearer wrong") is False assert module._authorized("Bearer wrong") is False
assert module._real_model("route/codex/gpt-5.6-sol/xhigh") == "gpt-5.6-sol" assert module._real_model("route/codex/gpt-5.6-sol/xhigh") == "gpt-5.6-sol"
payload = module._validate_payload( payload = module._validate_payload(
{"model": "gpt-5.6-terra", "store": True, "stream": False} {
"model": "gpt-5.6-terra",
"input": "route this chat turn",
"store": True,
"stream": False,
}
) )
assert payload["store"] is False assert payload["store"] is False
assert payload["stream"] is True assert payload["stream"] is True
assert payload["input"] == [
{
"type": "message",
"role": "user",
"content": [{"type": "input_text", "text": "route this chat turn"}],
}
]
response_item = {
"type": "message",
"role": "user",
"content": [{"type": "input_text", "text": "keep this item"}],
}
assert module._validate_payload(
{"model": "gpt-5.6-terra", "input": response_item}
)["input"] == [response_item]
response_items = [response_item]
assert module._validate_payload(
{"model": "gpt-5.6-terra", "input": response_items}
)["input"] is response_items
routed = module._validate_payload( routed = module._validate_payload(
{"model": "route/codex/gpt-5.6-luna/low", "stream": False} {
"model": "route/codex/gpt-5.6-luna/low",
"input": "use the low route",
"stream": False,
}
) )
assert routed["model"] == "gpt-5.6-luna" assert routed["model"] == "gpt-5.6-luna"
with pytest.raises(ValueError, match="unsupported Codex model"): with pytest.raises(ValueError, match="unsupported Codex model"):
module._validate_payload({"model": "unapproved-model"}) module._validate_payload({"model": "unapproved-model", "input": "hello"})
with pytest.raises(ValueError, match="non-empty Responses input"):
module._validate_payload({"model": "gpt-5.6-terra", "input": ""})
with pytest.raises(ValueError, match="non-empty Responses input list"):
module._validate_payload({"model": "gpt-5.6-terra", "input": []})
auth_dir = tmp_path / ".codex" auth_dir = tmp_path / ".codex"
auth_dir.mkdir() auth_dir.mkdir()