hermes: normalize Switchyard Codex inputs
This commit is contained in:
parent
ac61725dee
commit
a7feed5dba
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user