Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion products/tasks/backend/presentation/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2041,7 +2041,12 @@ class TaskRunBootstrapCreateRequestSerializer(
PR_AUTHORSHIP_MODE_CHOICES = [mode.value for mode in PrAuthorshipMode]
RUN_SOURCE_CHOICES = [source.value for source in RunSource]
RUNTIME_ADAPTER_CHOICES = [adapter.value for adapter in RuntimeAdapter]
REASONING_EFFORT_CHOICES = [effort.value for effort in PUBLIC_REASONING_EFFORTS]
PI_THINKING_LEVEL_CHOICES = ("off", "minimal", "low", "medium", "high", "xhigh", "max")
REASONING_EFFORT_CHOICES = [
"off",
"minimal",
*(effort.value for effort in PUBLIC_REASONING_EFFORTS),
]

environment = serializers.ChoiceField(
choices=[environment.value for environment in tasks_facade.TaskRunEnvironment],
Expand Down Expand Up @@ -2163,6 +2168,37 @@ def validate(self, attrs):
errors["relayed_mcp_servers"] = collision_error
initial_permission_mode = attrs.get("initial_permission_mode")
runtime_adapter = attrs.get("runtime_adapter")
view = self.context.get("view")
request = self.context.get("request")
task_id = view._task_id() if view else None
is_pi_task = (
task_id is not None
and request is not None
and tasks_facade.task_runtime(
task_id,
self.context["team"].id,
request.user.id,
for_control=True,
)
== tasks_facade.TaskRuntime.PI
)
if is_pi_task:
if runtime_adapter is not None:
errors["runtime_adapter"] = "This field cannot be used with a Pi task."
if attrs.get("context_window") is not None:
errors["context_window"] = "This field cannot be used with a Pi task."
if attrs.get("fast_mode") is not None:
errors["fast_mode"] = "This field cannot be used with a Pi task."
if initial_permission_mode is not None:
errors["initial_permission_mode"] = "This field cannot be used with a Pi task."
if (
attrs.get("reasoning_effort") is not None
and attrs["reasoning_effort"] not in self.PI_THINKING_LEVEL_CHOICES
):
errors["reasoning_effort"] = "This thinking level is not supported by Pi."
if errors:
raise serializers.ValidationError(errors)
return attrs
if initial_permission_mode is not None:
if runtime_adapter is None:
errors["initial_permission_mode"] = "This field requires runtime_adapter to be set."
Expand Down
37 changes: 37 additions & 0 deletions products/tasks/backend/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2206,6 +2206,43 @@ def test_create_run_endpoint_creates_cloud_run_without_triggering_workflow(self,
self.assertEqual(task_run.state["auto_publish"], True)
mock_workflow.assert_not_called()

def test_create_run_endpoint_uses_pi_model_without_an_acp_adapter(self):
task = self.create_task(runtime=Task.Runtime.PI)

response = self.client.post(
f"/api/projects/@current/tasks/{task.id}/runs/",
{
"environment": "cloud",
"mode": "interactive",
"model": "gpt-5.6-terra",
"reasoning_effort": "high",
},
format="json",
)

self.assertEqual(response.status_code, status.HTTP_201_CREATED)
task_run = TaskRun.objects.get(id=response.json()["id"])
self.assertEqual(task_run.state["model"], "gpt-5.6-terra")
self.assertEqual(task_run.state["reasoning_effort"], "high")
self.assertNotIn("runtime_adapter", task_run.state)

def test_create_run_endpoint_rejects_non_pi_thinking_level_for_pi_task(self):
task = self.create_task(runtime=Task.Runtime.PI)

response = self.client.post(
f"/api/projects/@current/tasks/{task.id}/runs/",
{
"environment": "cloud",
"mode": "interactive",
"model": "gpt-5.6-terra",
"reasoning_effort": "ultracode",
},
format="json",
)

self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn("reasoning_effort", response.json())

# is_url_allowed resolves DNS for real in CI, and example.com subdomains don't resolve.
@patch("products.tasks.backend.presentation.serializers.is_url_allowed", return_value=(True, None))
@patch("products.tasks.backend.temporal.client.execute_task_processing_workflow")
Expand Down
Loading