Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
639284f
Always include model field in response payload
Apr 14, 2026
ee071e9
Add e2e tests for model field always present in response
RaviPidaparthi Apr 14, 2026
9a7eda9
fix: DELETE response returns object='response' instead of 'response.d…
RaviPidaparthi Apr 14, 2026
fc57ffb
fix: stamp agent_session_id and conversation_id on to_snapshot fallback
RaviPidaparthi Apr 14, 2026
a141311
fix: remove output:list override breaking get_history deserialization
RaviPidaparthi Apr 15, 2026
d45481f
fix: use polymorphic _deserialize for OutputItem subtypes + contract …
RaviPidaparthi Apr 15, 2026
d6740b0
feat: deterministic session ID derivation from conversational context
RaviPidaparthi Apr 15, 2026
d8e167d
feat: strongly-typed return types for all emit_* builder methods
RaviPidaparthi Apr 15, 2026
655fecb
refactor: tighten OutputItemBuilder.emit_added/emit_done to accept Ou…
RaviPidaparthi Apr 15, 2026
05a0d15
refactor: tighten all public API parameters from dict to generated mo…
RaviPidaparthi Apr 15, 2026
19a773b
refactor: internalize escape-hatch methods and tighten remaining list…
RaviPidaparthi Apr 15, 2026
09db653
refactor: reduce public API surface — remove EVENT_TYPE alias, intern…
RaviPidaparthi Apr 15, 2026
32ae249
Merge remote-tracking branch 'origin/main' into fix/responses-model-a…
RaviPidaparthi Apr 15, 2026
ed734b5
fix: handle None agent_reference, add session ID length comment, remo…
RaviPidaparthi Apr 15, 2026
ce72a94
fix: fix Sphinx docstring warnings via models_patch (ResponseObject.o…
RaviPidaparthi Apr 15, 2026
937731f
fix: cap aiohttp<4.0.0 to avoid unstable 4.0.0a1 pre-release
RaviPidaparthi Apr 15, 2026
0cf5c34
fix: resolve pylint (line-too-long, protected-access) and pyright (in…
RaviPidaparthi Apr 15, 2026
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
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
from .models._helpers import (
get_conversation_id,
get_input_expanded,
to_output_item,
)
from .store._base import ResponseProviderProtocol, ResponseStreamProviderProtocol
from .store._foundry_errors import (
Expand Down Expand Up @@ -51,5 +50,4 @@
"ResponseObject",
"get_conversation_id",
"get_input_expanded",
"to_output_item",
]
Original file line number Diff line number Diff line change
Expand Up @@ -2,40 +2,8 @@
# Licensed under the MIT license.
"""HTTP hosting, routing, and request orchestration for the Responses server."""

from ._observability import (
CreateSpan,
CreateSpanHook,
InMemoryCreateSpanHook,
RecordedSpan,
build_create_span_tags,
build_platform_server_header,
start_create_span,
)
from ._routing import ResponsesAgentServerHost
from ._validation import (
build_api_error_response,
build_invalid_mode_error_response,
build_not_found_error_response,
parse_and_validate_create_response,
parse_create_response,
to_api_error_response,
validate_create_response,
)

__all__ = [
"ResponsesAgentServerHost",
"CreateSpan",
"CreateSpanHook",
"InMemoryCreateSpanHook",
"RecordedSpan",
"build_api_error_response",
"build_create_span_tags",
"build_invalid_mode_error_response",
"build_not_found_error_response",
"build_platform_server_header",
"parse_and_validate_create_response",
"parse_create_response",
"start_create_span",
"to_api_error_response",
"validate_create_response",
]
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,21 @@
from starlette.responses import JSONResponse, Response, StreamingResponse

from azure.ai.agentserver.core import detach_context, end_span, flush_spans, set_current_span, trace_stream
from azure.ai.agentserver.responses.models._generated import AgentReference, CreateResponse
from azure.ai.agentserver.responses.models._generated import (
AgentReference,
CreateResponse,
ResponseStreamEventType,
)

from .._options import ResponsesServerOptions
from .._response_context import IsolationContext, ResponseContext
from ..models._helpers import get_input_expanded, to_output_item
from ..models.errors import RequestValidationError
from ..models.runtime import ResponseExecution, ResponseModeFlags, build_cancelled_response, build_failed_response
from ..store._base import ResponseProviderProtocol, ResponseStreamProviderProtocol
from ..streaming._helpers import EVENT_TYPE, _encode_sse
from ..streaming._helpers import _encode_sse
from ..streaming._sse import encode_sse_any_event
from ..streaming._state_machine import normalize_lifecycle_events
from ..streaming._state_machine import _normalize_lifecycle_events
from ._execution_context import _ExecutionContext
from ._observability import (
CreateSpan,
Expand Down Expand Up @@ -208,11 +212,11 @@ def __init__(

# Validate the lifecycle event state machine on startup so
# misconfigured state machines surface immediately.
normalize_lifecycle_events(
_normalize_lifecycle_events(
response_id="resp_validation",
events=[
{"type": EVENT_TYPE.RESPONSE_CREATED.value, "response": {"status": "in_progress"}},
{"type": EVENT_TYPE.RESPONSE_COMPLETED.value, "response": {"status": "completed"}},
{"type": ResponseStreamEventType.RESPONSE_CREATED.value, "response": {"status": "in_progress"}},
{"type": ResponseStreamEventType.RESPONSE_COMPLETED.value, "response": {"status": "completed"}},
],
)

Expand Down Expand Up @@ -342,7 +346,7 @@ def _build_execution_context(
stream = bool(getattr(parsed, "stream", False))
store = True if getattr(parsed, "store", None) is None else bool(parsed.store)
background = bool(getattr(parsed, "background", False))
model = getattr(parsed, "model", None)
model = getattr(parsed, "model", None) or ""
_expanded = get_input_expanded(parsed)
input_items = [out for item in _expanded if (out := to_output_item(item, response_id)) is not None]
previous_response_id: str | None = (
Expand Down Expand Up @@ -469,7 +473,9 @@ async def handle_create(self, request: Request) -> Response: # pylint: disable=

# B39: Resolve session ID
config_session_id = getattr(getattr(self._host, "config", None), "session_id", "") or ""
agent_session_id = _resolve_session_id(parsed, payload, env_session_id=config_session_id)
agent_session_id = _resolve_session_id(
parsed, payload, env_session_id=config_session_id, agent_reference=agent_reference
)

ctx = self._build_execution_context(
parsed=parsed,
Expand Down Expand Up @@ -833,7 +839,7 @@ async def handle_delete(self, request: Request) -> Response:
)

return JSONResponse(
{"id": response_id, "object": "response.deleted", "deleted": True},
{"id": response_id, "object": "response", "deleted": True},
status_code=200,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
)
from ..store._base import ResponseProviderProtocol, ResponseStreamProviderProtocol
from ..streaming._helpers import (
EVENT_TYPE,
_apply_stream_event_defaults,
_build_events,
_coerce_handler_event,
Expand Down Expand Up @@ -126,20 +125,20 @@ async def _iter_with_winddown(

_OUTPUT_ITEM_EVENT_TYPES: frozenset[str] = frozenset(
{
EVENT_TYPE.RESPONSE_OUTPUT_ITEM_ADDED.value,
EVENT_TYPE.RESPONSE_OUTPUT_ITEM_DONE.value,
generated_models.ResponseStreamEventType.RESPONSE_OUTPUT_ITEM_ADDED.value,
generated_models.ResponseStreamEventType.RESPONSE_OUTPUT_ITEM_DONE.value,
}
)

# Response-level lifecycle events whose ``response`` field carries a full Response snapshot.
# Used by FR-008a output manipulation detection.
_RESPONSE_SNAPSHOT_TYPES: frozenset[str] = frozenset(
{
EVENT_TYPE.RESPONSE_IN_PROGRESS.value,
EVENT_TYPE.RESPONSE_COMPLETED.value,
EVENT_TYPE.RESPONSE_FAILED.value,
EVENT_TYPE.RESPONSE_INCOMPLETE.value,
EVENT_TYPE.RESPONSE_QUEUED.value,
generated_models.ResponseStreamEventType.RESPONSE_IN_PROGRESS.value,
generated_models.ResponseStreamEventType.RESPONSE_COMPLETED.value,
generated_models.ResponseStreamEventType.RESPONSE_FAILED.value,
generated_models.ResponseStreamEventType.RESPONSE_INCOMPLETE.value,
generated_models.ResponseStreamEventType.RESPONSE_QUEUED.value,
}
)

Expand Down Expand Up @@ -308,7 +307,8 @@ async def _run_background_non_stream( # pylint: disable=too-many-locals,too-man
record.response_created_signal.set()
else:
# Track output_item.added events for FR-008a
if normalized.get("type") == EVENT_TYPE.RESPONSE_OUTPUT_ITEM_ADDED.value:
_item_added = generated_models.ResponseStreamEventType.RESPONSE_OUTPUT_ITEM_ADDED
if normalized.get("type") == _item_added.value:
output_item_count += 1

# FR-008a: detect direct Output manipulation on response.* events
Expand Down Expand Up @@ -510,9 +510,9 @@ class _ResponseOrchestrator: # pylint: disable=too-many-instance-attributes

_TERMINAL_SSE_TYPES: frozenset[str] = frozenset(
{
EVENT_TYPE.RESPONSE_COMPLETED.value,
EVENT_TYPE.RESPONSE_FAILED.value,
EVENT_TYPE.RESPONSE_INCOMPLETE.value,
generated_models.ResponseStreamEventType.RESPONSE_COMPLETED.value,
generated_models.ResponseStreamEventType.RESPONSE_FAILED.value,
generated_models.ResponseStreamEventType.RESPONSE_INCOMPLETE.value,
}
)

Expand Down Expand Up @@ -619,7 +619,7 @@ async def _cancel_terminal_sse_dict(
:rtype: ResponseStreamEvent
"""
cancel_event: dict[str, Any] = {
"type": EVENT_TYPE.RESPONSE_FAILED.value,
"type": generated_models.ResponseStreamEventType.RESPONSE_FAILED.value,
"response": _build_cancelled_response(ctx.response_id, ctx.agent_reference, ctx.model).as_dict(),
}
return await self._normalize_and_append(ctx, state, cancel_event)
Expand All @@ -640,7 +640,7 @@ async def _make_failed_event(
:rtype: ResponseStreamEvent
"""
failed_event: dict[str, Any] = {
"type": EVENT_TYPE.RESPONSE_FAILED.value,
"type": generated_models.ResponseStreamEventType.RESPONSE_FAILED.value,
"response": {
"id": ctx.response_id,
"object": "response",
Expand Down Expand Up @@ -685,6 +685,8 @@ async def _register_bg_execution(
input_items=deepcopy(ctx.input_items),
previous_response_id=ctx.previous_response_id,
cancel_signal=ctx.cancellation_signal,
agent_session_id=ctx.agent_session_id,
conversation_id=ctx.conversation_id,
)
execution.set_response_snapshot(generated_models.ResponseObject(initial_payload))
execution.subject = _ResponseEventSubject()
Expand Down Expand Up @@ -899,7 +901,7 @@ async def _process_handler_events( # pylint: disable=too-many-return-statements
# appended to the state machine before we emit response.failed.
_pre_coerced = _coerce_handler_event(raw)
_pre_type = _pre_coerced.get("type", "")
if _pre_type == EVENT_TYPE.RESPONSE_OUTPUT_ITEM_ADDED.value:
if _pre_type == generated_models.ResponseStreamEventType.RESPONSE_OUTPUT_ITEM_ADDED.value:
output_item_count += 1
if _pre_type in _RESPONSE_SNAPSHOT_TYPES:
_pre_response = _pre_coerced.get("response") or {}
Expand Down Expand Up @@ -1105,6 +1107,8 @@ async def _finalize_stream(self, ctx: _ExecutionContext, state: _PipelineState)
input_items=deepcopy(ctx.input_items),
previous_response_id=ctx.previous_response_id,
cancel_signal=ctx.cancellation_signal if ctx.background else None,
agent_session_id=ctx.agent_session_id,
conversation_id=ctx.conversation_id,
)
execution.set_response_snapshot(generated_models.ResponseObject(response_payload))
await self._runtime_state.add(execution)
Expand Down Expand Up @@ -1381,6 +1385,8 @@ async def run_sync(self, ctx: _ExecutionContext) -> dict[str, Any]:
input_items=deepcopy(ctx.input_items),
previous_response_id=ctx.previous_response_id,
response_context=ctx.context,
agent_session_id=ctx.agent_session_id,
conversation_id=ctx.conversation_id,
)
record.set_response_snapshot(generated_models.ResponseObject(response_payload))

Expand Down Expand Up @@ -1444,6 +1450,8 @@ async def run_background(self, ctx: _ExecutionContext) -> dict[str, Any]:
cancel_signal=ctx.cancellation_signal,
initial_model=ctx.model,
initial_agent_reference=ctx.agent_reference,
agent_session_id=ctx.agent_session_id,
conversation_id=ctx.conversation_id,
)

# Register so GET can observe in-flight state
Expand Down
Loading
Loading