diff --git a/responses_api_agents/opencode_agent/app.py b/responses_api_agents/opencode_agent/app.py index 87603386eaa..366a02202a4 100644 --- a/responses_api_agents/opencode_agent/app.py +++ b/responses_api_agents/opencode_agent/app.py @@ -582,7 +582,9 @@ def _build_opencode_config(self, rollout_id: Optional[str] = None) -> dict[str, model, { "name": self.config.model, - "interleaved": {"field": "reasoning"}, + # Gym model servers emit and accept `reasoning_content`; OpenCode replays assistant + # history under this field, and Gym rejects an unknown `reasoning` key with a 422. + "interleaved": {"field": "reasoning_content"}, "limit": {"context": self.config.context_window, "output": self.config.max_output_tokens}, }, ) diff --git a/responses_api_agents/opencode_agent/tests/test_app.py b/responses_api_agents/opencode_agent/tests/test_app.py index 9d0a7bb1721..1dcf0de12f7 100644 --- a/responses_api_agents/opencode_agent/tests/test_app.py +++ b/responses_api_agents/opencode_agent/tests/test_app.py @@ -22,6 +22,7 @@ from nemo_gym.config_types import ModelServerRef, ResourcesServerRef from nemo_gym.openai_utils import ( + NeMoGymChatCompletionCreateParamsNonStreaming, NeMoGymEasyInputMessage, NeMoGymFunctionCallOutput, NeMoGymResponseCreateParamsNonStreaming, @@ -391,6 +392,30 @@ def test_model_server_builds_local_provider(self) -> None: assert provider["options"]["baseURL"] == "http://model/v1" assert provider["models"]["Qwen3.6-35B-A3B"]["limit"]["output"] == 131072 + def test_model_server_replayed_reasoning_is_accepted_by_gym_chat_completions(self) -> None: + agent = _make_agent(model="m", model_server=ModelServerRef(type="responses_api_models", name="policy_model")) + with patch.object(agent, "_resolve_model_base_url", return_value="http://model/v1"): + field = agent._build_opencode_config()["provider"]["nemo"]["models"]["m"]["interleaved"]["field"] + + # OpenCode replays an assistant turn after a tool call with the interleaved field set. + replayed = { + "role": "assistant", + "content": "", + field: "thinking", + "tool_calls": [{"id": "c1", "type": "function", "function": {"name": "task", "arguments": "{}"}}], + } + body = NeMoGymChatCompletionCreateParamsNonStreaming.model_validate( + { + "model": "m", + "messages": [ + {"role": "user", "content": "hi"}, + replayed, + {"role": "tool", "tool_call_id": "c1", "content": "done"}, + ], + } + ) + assert body.messages[1]["reasoning_content"] == "thinking" + class TestRolloutObservability: def test_routes_model_server_without_mutating_config(self, tmp_path: Path) -> None: