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
2 changes: 2 additions & 0 deletions contributors/emails/seraphine@Seraphines-Mac-Studio.local
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Bartok9
# Seraphine Mac Studio local email on Bartok9 PR tips (per-PR attribution; Teknium/Daniel 2026-08-01)
43 changes: 43 additions & 0 deletions gateway/platforms/api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ def _hermes_version() -> str:
DEFAULT_HOST = "127.0.0.1"
DEFAULT_PORT = 8642
MAX_STORED_RESPONSES = 100
MAX_PREVIOUS_RESPONSE_ID_LENGTH = 128
MAX_REQUEST_BYTES = 10_000_000 # 10 MB — accommodates long agent conversations with tool calls
CHAT_COMPLETIONS_SSE_KEEPALIVE_SECONDS = 30.0
MAX_NORMALIZED_TEXT_LENGTH = 65_536 # 64 KB cap for normalized content parts
Expand Down Expand Up @@ -4891,6 +4892,27 @@ async def _handle_responses(self, request: "web.Request") -> "web.Response":

instructions = body.get("instructions")
previous_response_id = body.get("previous_response_id")

# Basic hardening: ensure previous_response_id is a reasonable string.
if previous_response_id is not None:
if not isinstance(previous_response_id, str):
return web.json_response(
_openai_error("Invalid 'previous_response_id' type"),
status=400,
)
if not previous_response_id:
return web.json_response(
_openai_error("Invalid 'previous_response_id' value"),
status=400,
)
if len(previous_response_id) > MAX_PREVIOUS_RESPONSE_ID_LENGTH:
return web.json_response(
_openai_error(
"previous_response_id is too long.",
code="previous_response_id_too_long",
),
status=400,
)
conversation = body.get("conversation")
store = _coerce_request_bool(body.get("store"), default=True)

Expand Down Expand Up @@ -6110,6 +6132,27 @@ async def _handle_runs(self, request: "web.Request") -> "web.Response":
instructions = body.get("instructions")
previous_response_id = body.get("previous_response_id")

# Basic hardening: ensure previous_response_id is a reasonable string.
if previous_response_id is not None:
if not isinstance(previous_response_id, str):
return web.json_response(
_openai_error("Invalid 'previous_response_id' type"),
status=400,
)
if not previous_response_id:
return web.json_response(
_openai_error("Invalid 'previous_response_id' value"),
status=400,
)
if len(previous_response_id) > MAX_PREVIOUS_RESPONSE_ID_LENGTH:
return web.json_response(
_openai_error(
"previous_response_id is too long.",
code="previous_response_id_too_long",
),
status=400,
)

# Accept explicit conversation_history from the request body.
# Precedence: explicit conversation_history > previous_response_id.
conversation_history: List[Dict[str, str]] = []
Expand Down
2 changes: 2 additions & 0 deletions tests/gateway/test_api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from gateway.config import GatewayConfig, Platform, PlatformConfig
from gateway.platforms.api_server import (
APIServerAdapter,
MAX_PREVIOUS_RESPONSE_ID_LENGTH,
ResponseStore,
_IdempotencyCache,
_derive_chat_session_id,
Expand Down Expand Up @@ -318,6 +319,7 @@ def _create_app(adapter: APIServerAdapter) -> web.Application:
app.router.add_post("/v1/responses", adapter._handle_responses)
app.router.add_get("/v1/responses/{response_id}", adapter._handle_get_response)
app.router.add_delete("/v1/responses/{response_id}", adapter._handle_delete_response)
app.router.add_post("/v1/runs", adapter._handle_runs)
app.router.add_post(
"/api/platforms/{platform}/events",
adapter._handle_platform_event_callback,
Expand Down
Loading