@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / ec1d30c0f6
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 7, 19:00 UTC

[Responses] Decouple SSE event helpers from Harmony context (#35148)

Signed-off-by: sfeng33 <4florafeng@gmail.com>

Flora Feng committed Feb 24, 2026 at 23:05 UTC ec1d30c0f6fd80f5fcf20f5053e645d9347b313e
5 files changed +527 -565
tests/entrypoints/openai/responses/conftest.py
+182 -20
@@ -39,6 +39,7 @@ def pairs_of_event_types() -> dict[str, str]:
39 "response.mcp_call.completed": "response.mcp_call.in_progress",
40 "response.function_call_arguments.done": "response.function_call_arguments.delta", # noqa: E501
41 "response.code_interpreter_call_code.done": "response.code_interpreter_call_code.delta", # noqa: E501
42 + "response.code_interpreter_call.completed": "response.code_interpreter_call.in_progress", # noqa: E501
43 "response.web_search_call.completed": "response.web_search_call.in_progress",
44 }
45 # fmt: on
@@ -108,29 +109,19 @@ def events_contain_type(events: list, type_substring: str) -> bool:
109 return any(type_substring in getattr(e, "type", "") for e in events)
110
111
111 -def validate_streaming_event_stack(
112 - events: list, pairs_of_event_types: dict[str, str]
113 -) -> None:
114 - """Validate that streaming events are properly nested/paired."""
112 +def _validate_event_pairing(events: list, pairs_of_event_types: dict[str, str]) -> None:
113 + """Validate that streaming events are properly nested/paired.
114 +
115 + Derives push/pop sets from *pairs_of_event_types* so that every
116 + start/end pair in the dict is handled automatically.
117 + """
118 + start_events = set(pairs_of_event_types.values())
119 + end_events = set(pairs_of_event_types.keys())
120 +
121 stack: list[str] = []
122 for event in events:
123 etype = event.type
118 - if etype == "response.created":
119 - stack.append(etype)
120 - elif etype == "response.completed":
121 - assert stack and stack[-1] == pairs_of_event_types[etype], (
122 - f"Unexpected stack top for {etype}: "
123 - f"got {stack[-1] if stack else '<empty>'}"
124 - )
125 - stack.pop()
126 - elif etype.endswith("added") or etype == "response.mcp_call.in_progress":
127 - stack.append(etype)
128 - elif etype.endswith("delta"):
129 - if stack and stack[-1] == etype:
130 - continue
131 - stack.append(etype)
132 - elif etype.endswith("done") or etype == "response.mcp_call.completed":
133 - assert etype in pairs_of_event_types, f"Unknown done event: {etype}"
124 + if etype in end_events:
125 expected_start = pairs_of_event_types[etype]
126 assert stack and stack[-1] == expected_start, (
127 f"Stack mismatch for {etype}: "
@@ -138,9 +129,180 @@ def validate_streaming_event_stack(
129 f"got {stack[-1] if stack else '<empty>'}"
130 )
131 stack.pop()
132 + elif etype in start_events:
133 + # Consecutive deltas of the same type share a single stack slot.
134 + if etype.endswith("delta") and stack and stack[-1] == etype:
135 + continue
136 + stack.append(etype)
137 + # else: passthrough event (e.g. response.in_progress,
138 + # web_search_call.searching, code_interpreter_call.interpreting)
139 assert len(stack) == 0, f"Unclosed events on stack: {stack}"
140
141
142 +def _validate_event_ordering(events: list) -> None:
143 + """Validate that envelope events appear in the correct positions."""
144 + assert len(events) >= 2, f"Expected at least 2 events, got {len(events)}"
145 +
146 + # First event must be response.created
147 + assert events[0].type == "response.created", (
148 + f"First event must be response.created, got {events[0].type}"
149 + )
150 + # Last event must be response.completed
151 + assert events[-1].type == "response.completed", (
152 + f"Last event must be response.completed, got {events[-1].type}"
153 + )
154 +
155 + # response.in_progress, if present, must be the second event
156 + in_progress_indices = [
157 + i for i, e in enumerate(events) if e.type == "response.in_progress"
158 + ]
159 + if in_progress_indices:
160 + assert in_progress_indices == [1], (
161 + f"response.in_progress must be the second event, "
162 + f"found at indices {in_progress_indices}"
163 + )
164 +
165 + # Exactly one created and one completed
166 + created_count = sum(1 for e in events if e.type == "response.created")
167 + completed_count = sum(1 for e in events if e.type == "response.completed")
168 + assert created_count == 1, (
169 + f"Expected exactly 1 response.created, got {created_count}"
170 + )
171 + assert completed_count == 1, (
172 + f"Expected exactly 1 response.completed, got {completed_count}"
173 + )
174 +
175 +
176 +def _validate_field_consistency(events: list) -> None:
177 + """Validate item_id, output_index, and content_index consistency.
178 +
179 + Tracks the active output item established by ``output_item.added``
180 + and verifies that all subsequent events for that item carry matching
181 + identifiers until ``output_item.done`` closes it.
182 + """
183 + _SESSION_EVENTS = {
184 + "response.created",
185 + "response.in_progress",
186 + "response.completed",
187 + }
188 +
189 + active_item_id: str | None = None
190 + active_output_index: int | None = None
191 + last_output_index: int = -1
192 + active_content_index: int | None = None
193 +
194 + for event in events:
195 + etype = event.type
196 +
197 + if etype in _SESSION_EVENTS:
198 + continue
199 +
200 + # --- output_item.added: opens a new item ------------------
201 + if etype == "response.output_item.added":
202 + item = getattr(event, "item", None)
203 + output_index = getattr(event, "output_index", None)
204 +
205 + assert item is not None, "output_item.added must have an item"
206 + item_id = getattr(item, "id", None)
207 + assert item_id, "output_item.added item must have an id"
208 +
209 + # output_index must be non-decreasing across items
210 + if output_index is not None:
211 + assert output_index >= last_output_index, (
212 + f"output_index went backwards: {output_index} < {last_output_index}"
213 + )
214 + last_output_index = output_index
215 +
216 + active_item_id = item_id
217 + active_output_index = output_index
218 + active_content_index = None
219 + continue
220 +
221 + # --- output_item.done: closes the active item -------------
222 + if etype == "response.output_item.done":
223 + item = getattr(event, "item", None)
224 + output_index = getattr(event, "output_index", None)
225 +
226 + assert item is not None, "output_item.done must have an item"
227 + done_item_id = getattr(item, "id", None)
228 +
229 + if active_item_id is not None and done_item_id:
230 + assert done_item_id == active_item_id, (
231 + f"output_item.done item.id mismatch: "
232 + f"expected {active_item_id}, got {done_item_id}"
233 + )
234 + if active_output_index is not None and output_index is not None:
235 + assert output_index == active_output_index, (
236 + f"output_item.done output_index mismatch: "
237 + f"expected {active_output_index}, got {output_index}"
238 + )
239 +
240 + active_item_id = None
241 + active_output_index = None
242 + active_content_index = None
243 + continue
244 +
245 + # --- content_part / reasoning_part added: sets content_index
246 + if etype in (
247 + "response.content_part.added",
248 + "response.reasoning_part.added",
249 + ):
250 + _assert_item_fields(event, etype, active_item_id, active_output_index)
251 + active_content_index = getattr(event, "content_index", None)
252 + continue
253 +
254 + # --- all other item-level events --------------------------
255 + _assert_item_fields(event, etype, active_item_id, active_output_index)
256 +
257 + # content_index (only meaningful on events that carry it)
258 + content_index = getattr(event, "content_index", None)
259 + if content_index is not None and active_content_index is not None:
260 + assert content_index == active_content_index, (
261 + f"{etype} content_index mismatch: "
262 + f"expected {active_content_index}, got {content_index}"
263 + )
264 +
265 +
266 +def _assert_item_fields(
267 + event,
268 + etype: str,
269 + active_item_id: str | None,
270 + active_output_index: int | None,
271 +) -> None:
272 + """Check that *event*'s item_id and output_index match the active item."""
273 + event_item_id = getattr(event, "item_id", None)
274 + output_index = getattr(event, "output_index", None)
275 +
276 + if active_item_id is not None and event_item_id is not None:
277 + assert event_item_id == active_item_id, (
278 + f"{etype} item_id mismatch: expected {active_item_id}, got {event_item_id}"
279 + )
280 + if active_output_index is not None and output_index is not None:
281 + assert output_index == active_output_index, (
282 + f"{etype} output_index mismatch: "
283 + f"expected {active_output_index}, got {output_index}"
284 + )
285 +
286 +
287 +def validate_streaming_event_stack(
288 + events: list, pairs_of_event_types: dict[str, str]
289 +) -> None:
290 + """Validate streaming events: pairing, ordering, and field consistency.
291 +
292 + Checks three aspects:
293 + 1. **Event pairing** — start/end events are properly nested
294 + (stack-based matching derived from *pairs_of_event_types*).
295 + 2. **Event ordering** — envelope events (``created``,
296 + ``in_progress``, ``completed``) appear at the correct positions.
297 + 3. **Field consistency** — ``item_id``, ``output_index``, and
298 + ``content_index`` are consistent across related events within
299 + each output item's lifecycle.
300 + """
301 + _validate_event_pairing(events, pairs_of_event_types)
302 + _validate_event_ordering(events)
303 + _validate_field_consistency(events)
304 +
305 +
306 def log_response_diagnostics(
307 response,
308 *,
tests/entrypoints/openai/responses/test_harmony.py
+24 -43
@@ -910,21 +910,25 @@ async def test_function_calling_no_code_interpreter_events(
910 reason="This test is flaky in CI, needs investigation and "
911 "potential fixes in the code interpreter MCP implementation."
912 )
913 -async def test_mcp_code_interpreter_streaming(client: OpenAI, model_name: str, server):
914 - tools = [{"type": "mcp", "server_label": "code_interpreter"}]
913 +async def test_code_interpreter_streaming(
914 + client: OpenAI,
915 + model_name: str,
916 + pairs_of_event_types: dict[str, str],
917 +):
918 + tools = [{"type": "code_interpreter", "container": {"type": "auto"}}]
919 input_text = (
920 "Calculate 123 * 456 using python. "
921 "The python interpreter is not stateful and you must "
922 "print to see the output."
923 )
924
921 - def _has_mcp_call(evts: list) -> bool:
922 - return events_contain_type(evts, "mcp_call")
925 + def _has_code_interpreter(evts: list) -> bool:
926 + return events_contain_type(evts, "code_interpreter")
927
928 events = await retry_streaming_for(
929 client,
930 model=model_name,
927 - validate_events=_has_mcp_call,
931 + validate_events=_has_code_interpreter,
932 input=input_text,
933 tools=tools,
934 temperature=0.0,
@@ -936,59 +940,36 @@ async def test_mcp_code_interpreter_streaming(client: OpenAI, model_name: str, s
940 event_types = [e.type for e in events]
941 event_types_set = set(event_types)
942 logger.info(
939 - "\n====== MCP Streaming Diagnostics ======\n"
943 + "\n====== Code Interpreter Streaming Diagnostics ======\n"
944 "Event count: %d\n"
945 "Event types (in order): %s\n"
946 "Unique event types: %s\n"
943 - "=======================================",
947 + "====================================================",
948 len(events),
949 event_types,
950 sorted(event_types_set),
951 )
952
949 - # Verify the full MCP streaming lifecycle
950 - assert "response.output_item.added" in event_types_set, (
951 - f"MCP call was not added. Events: {sorted(event_types_set)}"
952 - )
953 - assert "response.mcp_call.in_progress" in event_types_set, (
954 - f"MCP call in_progress not seen. Events: {sorted(event_types_set)}"
955 - )
956 - assert "response.mcp_call_arguments.delta" in event_types_set, (
957 - f"MCP arguments delta not seen. Events: {sorted(event_types_set)}"
958 - )
959 - assert "response.mcp_call_arguments.done" in event_types_set, (
960 - f"MCP arguments done not seen. Events: {sorted(event_types_set)}"
961 - )
962 - assert "response.mcp_call.completed" in event_types_set, (
963 - f"MCP call completed not seen. Events: {sorted(event_types_set)}"
964 - )
965 - assert "response.output_item.done" in event_types_set, (
966 - f"MCP item done not seen. Events: {sorted(event_types_set)}"
967 - )
953 + # Structural validation (pairing, ordering, field consistency)
954 + validate_streaming_event_stack(events, pairs_of_event_types)
955
969 - # Validate specific MCP event details
956 + # Validate code interpreter item fields
957 for event in events:
971 - if event.type == "response.output_item.added":
972 - if hasattr(event.item, "type") and event.item.type == "mcp_call":
973 - assert event.item.name == "python"
974 - assert event.item.server_label == "code_interpreter"
975 - elif event.type == "response.mcp_call_arguments.done":
976 - assert event.name == "python"
977 - assert event.arguments is not None
958 + if (
959 + event.type == "response.output_item.added"
960 + and hasattr(event.item, "type")
961 + and event.item.type == "code_interpreter_call"
962 + ):
963 + assert event.item.status == "in_progress"
964 + elif event.type == "response.code_interpreter_call_code.done":
965 + assert event.code is not None
966 elif (
967 event.type == "response.output_item.done"
968 and hasattr(event.item, "type")
981 - and event.item.type == "mcp_call"
969 + and event.item.type == "code_interpreter_call"
970 ):
983 - assert event.item.name == "python"
971 assert event.item.status == "completed"
985 -
986 - # code_interpreter events should NOT appear when using MCP type
987 - code_interp_events = [e.type for e in events if "code_interpreter" in e.type]
988 - assert not code_interp_events, (
989 - "Should not see code_interpreter events when using MCP type, "
990 - f"but got: {code_interp_events}"
991 - )
972 + assert event.item.code is not None
973
974
975 @pytest.mark.asyncio
tests/entrypoints/openai/responses/test_mcp_tools.py
-78
@@ -241,81 +241,3 @@ class TestMCPEnabled:
241 )
242
243 validate_streaming_event_stack(events, pairs_of_event_types)
244 -
245 - assert events_contain_type(events, "mcp_call"), (
246 - f"No mcp_call events after retries. "
247 - f"Event types: {sorted({e.type for e in events})}"
248 - )
249 -
250 -
251 -class TestMCPDisabled:
252 - """Tests that MCP tools are not executed when the env flag is unset."""
253 -
254 - @pytest.fixture(scope="class")
255 - def mcp_disabled_server(self):
256 - env_dict = {
257 - **BASE_TEST_ENV,
258 - "VLLM_ENABLE_RESPONSES_API_STORE": "1",
259 - "PYTHON_EXECUTION_BACKEND": "dangerously_use_uv",
260 - "VLLM_GPT_OSS_HARMONY_SYSTEM_INSTRUCTIONS": "1",
261 - }
262 - with RemoteOpenAIServer(
263 - MODEL_NAME, list(_BASE_SERVER_ARGS), env_dict=env_dict
264 - ) as remote_server:
265 - yield remote_server
266 -
267 - @pytest_asyncio.fixture
268 - async def client(self, mcp_disabled_server):
269 - async with mcp_disabled_server.get_async_client() as async_client:
270 - yield async_client
271 -
272 - @pytest.mark.asyncio
273 - @pytest.mark.parametrize("model_name", [MODEL_NAME])
274 - async def test_mcp_disabled_server_does_not_execute(
275 - self, client: OpenAI, model_name: str
276 - ):
277 - """When MCP is disabled the model may still attempt tool calls
278 - (tool descriptions can remain in the prompt), but the server
279 - must NOT execute them."""
280 - response = await client.responses.create(
281 - model=model_name,
282 - input=(
283 - "Execute the following code if the tool is present: "
284 - "import random; print(random.randint(1, 1000000))"
285 - ),
286 - tools=[
287 - {
288 - "type": "mcp",
289 - "server_label": "code_interpreter",
290 - "server_url": "http://localhost:8888",
291 - }
292 - ],
293 - temperature=0.0,
294 - extra_body={"enable_response_messages": True},
295 - )
296 - assert response is not None
297 - assert response.status == "completed"
298 -
299 - log_response_diagnostics(response, label="MCP Disabled")
300 -
301 - # Server must not have executed any tool calls
302 - for message in response.output_messages:
303 - author = message.get("author", {})
304 - assert not (
305 - author.get("role") == "tool"
306 - and (author.get("name") or "").startswith("python")
307 - ), (
308 - "Server executed a python tool call even though MCP is "
309 - f"disabled. Message: {message}"
310 - )
311 -
312 - # No completed mcp_call output items
313 - for item in response.output:
314 - if getattr(item, "type", None) == "mcp_call":
315 - assert getattr(item, "status", None) != "completed", (
316 - "MCP call should not be completed when MCP is disabled"
317 - )
318 -
319 - # No developer messages injected
320 - for message in response.input_messages:
321 - assert message.get("author", {}).get("role") != "developer"
vllm/entrypoints/openai/responses/serving.py
+2 -2
@@ -89,7 +89,7 @@ from vllm.entrypoints.openai.responses.protocol import (
89 StreamingResponsesResponse,
90 )
91 from vllm.entrypoints.openai.responses.streaming_events import (
92 - HarmonyStreamingState,
92 + StreamingState,
93 emit_content_delta_events,
94 emit_previous_item_done_events,
95 emit_tool_action_events,
@@ -1591,7 +1591,7 @@ class OpenAIServingResponses(OpenAIServing):
1591 [StreamingResponsesResponse], StreamingResponsesResponse
1592 ],
1593 ) -> AsyncGenerator[StreamingResponsesResponse, None]:
1594 - state = HarmonyStreamingState()
1594 + state = StreamingState()
1595
1596 async for ctx in result_generator:
1597 assert isinstance(ctx, StreamingHarmonyContext)
vllm/entrypoints/openai/responses/streaming_events.py
+319 -422
@@ -6,6 +6,13 @@ Streaming SSE event builders for the Responses API.
6 Pure functions that translate streaming state + delta data into
7 OpenAI Response API SSE events. Used by the streaming event
8 processors in serving.py.
9 +
10 +The file is organized as:
11 + 1. StreamingState dataclass + utility helpers
12 + 2. Shared leaf helpers — delta events (take plain strings, no context)
13 + 3. Shared leaf helpers — done events (take plain strings, no context)
14 + 4. Harmony-specific dispatchers (route ctx/previous_item → leaf helpers)
15 + 5. Harmony-specific tool lifecycle helpers
16 """
17
18 import json
@@ -47,6 +54,7 @@ from openai.types.responses.response_output_item import McpCall
54 from openai.types.responses.response_reasoning_item import (
55 Content as ResponseReasoningTextContent,
56 )
57 +from openai_harmony import Message as HarmonyMessage
58
59 from vllm.entrypoints.mcp.tool_server import ToolServer
60 from vllm.entrypoints.openai.responses.context import StreamingHarmonyContext
@@ -64,13 +72,28 @@ TOOL_NAME_TO_MCP_SERVER_LABEL: Final[dict[str, str]] = {
72 }
73
74
75 +def _resolve_mcp_name_label(recipient: str) -> tuple[str, str]:
76 + """Resolve MCP tool name and server label from a recipient string.
77 +
78 + - ``mcp.*`` recipients: strip prefix, use the bare name as both
79 + name and server_label.
80 + - Everything else: use the recipient as the name and look up the
81 + server_label in TOOL_NAME_TO_MCP_SERVER_LABEL.
82 + """
83 + if recipient.startswith("mcp."):
84 + name = recipient[len("mcp.") :]
85 + return name, name
86 + return recipient, TOOL_NAME_TO_MCP_SERVER_LABEL.get(recipient, recipient)
87 +
88 +
89 @dataclass
68 -class HarmonyStreamingState:
69 - """Mutable state for harmony streaming event processing."""
90 +class StreamingState:
91 + """Mutable state for streaming event processing."""
92
93 current_content_index: int = -1
94 current_output_index: int = 0
95 current_item_id: str = ""
96 + current_call_id: str = ""
97 sent_output_item_added: bool = False
98 is_first_function_call_delta: bool = False
99
@@ -79,6 +102,7 @@ class HarmonyStreamingState:
102 self.current_output_index += 1
103 self.sent_output_item_added = False
104 self.is_first_function_call_delta = False
105 + self.current_call_id = ""
106
107
108 def is_mcp_tool_by_namespace(recipient: str | None) -> bool:
@@ -96,213 +120,16 @@ def is_mcp_tool_by_namespace(recipient: str | None) -> bool:
120 return not recipient.startswith("functions.")
121
122
99 -def emit_function_call_done_events(
100 - previous_item,
101 - state: HarmonyStreamingState,
102 -) -> list[StreamingResponsesResponse]:
103 - """Emit events when a function call completes."""
104 - function_name = previous_item.recipient[len("functions.") :]
105 - events: list[StreamingResponsesResponse] = []
106 - events.append(
107 - ResponseFunctionCallArgumentsDoneEvent(
108 - type="response.function_call_arguments.done",
109 - arguments=previous_item.content[0].text,
110 - name=function_name,
111 - item_id=state.current_item_id,
112 - output_index=state.current_output_index,
113 - sequence_number=-1,
114 - )
115 - )
116 - function_call_item = ResponseFunctionToolCall(
117 - type="function_call",
118 - arguments=previous_item.content[0].text,
119 - name=function_name,
120 - item_id=state.current_item_id,
121 - output_index=state.current_output_index,
122 - sequence_number=-1,
123 - call_id=f"fc_{random_uuid()}",
124 - status="completed",
125 - )
126 - events.append(
127 - ResponseOutputItemDoneEvent(
128 - type="response.output_item.done",
129 - sequence_number=-1,
130 - output_index=state.current_output_index,
131 - item=function_call_item,
132 - )
133 - )
134 - return events
135 -
136 -
137 -def emit_mcp_call_done_events(
138 - previous_item,
139 - state: HarmonyStreamingState,
140 -) -> list[StreamingResponsesResponse]:
141 - """Emit events when an MCP tool call completes."""
142 - server_label = TOOL_NAME_TO_MCP_SERVER_LABEL.get(
143 - previous_item.recipient, previous_item.recipient
144 - )
145 - events: list[StreamingResponsesResponse] = []
146 - events.append(
147 - ResponseMcpCallArgumentsDoneEvent(
148 - type="response.mcp_call_arguments.done",
149 - arguments=previous_item.content[0].text,
150 - name=previous_item.recipient,
151 - item_id=state.current_item_id,
152 - output_index=state.current_output_index,
153 - sequence_number=-1,
154 - )
155 - )
156 - events.append(
157 - ResponseMcpCallCompletedEvent(
158 - type="response.mcp_call.completed",
159 - sequence_number=-1,
160 - output_index=state.current_output_index,
161 - item_id=state.current_item_id,
162 - )
163 - )
164 - events.append(
165 - ResponseOutputItemDoneEvent(
166 - type="response.output_item.done",
167 - sequence_number=-1,
168 - output_index=state.current_output_index,
169 - item=McpCall(
170 - type="mcp_call",
171 - arguments=previous_item.content[0].text,
172 - name=previous_item.recipient,
173 - id=state.current_item_id,
174 - server_label=server_label,
175 - status="completed",
176 - ),
177 - )
178 - )
179 - return events
180 -
181 -
182 -def emit_reasoning_done_events(
183 - previous_item,
184 - state: HarmonyStreamingState,
185 -) -> list[StreamingResponsesResponse]:
186 - """Emit events when a reasoning (analysis) item completes."""
187 - content = ResponseReasoningTextContent(
188 - text=previous_item.content[0].text,
189 - type="reasoning_text",
190 - )
191 - reasoning_item = ResponseReasoningItem(
192 - type="reasoning",
193 - content=[content],
194 - status="completed",
195 - id=state.current_item_id,
196 - summary=[],
197 - )
198 - events: list[StreamingResponsesResponse] = []
199 - events.append(
200 - ResponseReasoningTextDoneEvent(
201 - type="response.reasoning_text.done",
202 - item_id=state.current_item_id,
203 - sequence_number=-1,
204 - output_index=state.current_output_index,
205 - content_index=state.current_content_index,
206 - text=previous_item.content[0].text,
207 - )
208 - )
209 - events.append(
210 - ResponseReasoningPartDoneEvent(
211 - type="response.reasoning_part.done",
212 - sequence_number=-1,
213 - item_id=state.current_item_id,
214 - output_index=state.current_output_index,
215 - content_index=state.current_content_index,
216 - part=content,
217 - )
218 - )
219 - events.append(
220 - ResponseOutputItemDoneEvent(
221 - type="response.output_item.done",
222 - sequence_number=-1,
223 - output_index=state.current_output_index,
224 - item=reasoning_item,
225 - )
226 - )
227 - return events
228 -
229 -
230 -def emit_text_output_done_events(
231 - previous_item,
232 - state: HarmonyStreamingState,
233 -) -> list[StreamingResponsesResponse]:
234 - """Emit events when a final text output item completes."""
235 - text_content = ResponseOutputText(
236 - type="output_text",
237 - text=previous_item.content[0].text,
238 - annotations=[],
239 - )
240 - events: list[StreamingResponsesResponse] = []
241 - events.append(
242 - ResponseTextDoneEvent(
243 - type="response.output_text.done",
244 - sequence_number=-1,
245 - output_index=state.current_output_index,
246 - content_index=state.current_content_index,
247 - text=previous_item.content[0].text,
248 - logprobs=[],
249 - item_id=state.current_item_id,
250 - )
251 - )
252 - events.append(
253 - ResponseContentPartDoneEvent(
254 - type="response.content_part.done",
255 - sequence_number=-1,
256 - item_id=state.current_item_id,
257 - output_index=state.current_output_index,
258 - content_index=state.current_content_index,
259 - part=text_content,
260 - )
261 - )
262 - events.append(
263 - ResponseOutputItemDoneEvent(
264 - type="response.output_item.done",
265 - sequence_number=-1,
266 - output_index=state.current_output_index,
267 - item=ResponseOutputMessage(
268 - id=state.current_item_id,
269 - type="message",
270 - role="assistant",
271 - content=[text_content],
272 - status="completed",
273 - ),
274 - )
275 - )
276 - return events
277 -
278 -
279 -def emit_previous_item_done_events(
280 - previous_item,
281 - state: HarmonyStreamingState,
282 -) -> list[StreamingResponsesResponse]:
283 - """Emit done events for the previous item when expecting a new start."""
284 - if previous_item.recipient is not None:
285 - # Deal with tool call
286 - if previous_item.recipient.startswith("functions."):
287 - return emit_function_call_done_events(previous_item, state)
288 - elif (
289 - is_mcp_tool_by_namespace(previous_item.recipient)
290 - and state.current_item_id is not None
291 - and state.current_item_id.startswith("mcp_")
292 - ):
293 - return emit_mcp_call_done_events(previous_item, state)
294 - elif previous_item.channel == "analysis":
295 - return emit_reasoning_done_events(previous_item, state)
296 - elif previous_item.channel == "final":
297 - return emit_text_output_done_events(previous_item, state)
298 - return []
123 +# =====================================================================
124 +# Shared leaf helpers — delta events
125 +# =====================================================================
126
127
301 -def emit_final_channel_delta_events(
302 - ctx: StreamingHarmonyContext,
303 - state: HarmonyStreamingState,
128 +def emit_text_delta_events(
129 + delta: str,
130 + state: StreamingState,
131 ) -> list[StreamingResponsesResponse]:
305 - """Emit events for final channel text delta streaming."""
132 + """Emit events for text content delta streaming."""
133 events: list[StreamingResponsesResponse] = []
134 if not state.sent_output_item_added:
135 state.sent_output_item_added = True
@@ -344,7 +171,7 @@ def emit_final_channel_delta_events(
171 content_index=state.current_content_index,
172 output_index=state.current_output_index,
173 item_id=state.current_item_id,
347 - delta=ctx.last_content_delta,
174 + delta=delta,
175 # TODO, use logprobs from ctx.last_request_output
176 logprobs=[],
177 )
@@ -352,11 +179,11 @@ def emit_final_channel_delta_events(
179 return events
180
181
355 -def emit_analysis_channel_delta_events(
356 - ctx: StreamingHarmonyContext,
357 - state: HarmonyStreamingState,
182 +def emit_reasoning_delta_events(
183 + delta: str,
184 + state: StreamingState,
185 ) -> list[StreamingResponsesResponse]:
359 - """Emit events for analysis channel reasoning delta streaming."""
186 + """Emit events for reasoning text delta streaming."""
187 events: list[StreamingResponsesResponse] = []
188 if not state.sent_output_item_added:
189 state.sent_output_item_added = True
@@ -394,20 +221,60 @@ def emit_analysis_channel_delta_events(
221 item_id=state.current_item_id,
222 output_index=state.current_output_index,
223 content_index=state.current_content_index,
397 - delta=ctx.last_content_delta,
224 + delta=delta,
225 sequence_number=-1,
226 )
227 )
228 return events
229
230
404 -def emit_mcp_tool_delta_events(
405 - ctx: StreamingHarmonyContext,
406 - state: HarmonyStreamingState,
231 +def emit_function_call_delta_events(
232 + delta: str,
233 + function_name: str,
234 + state: StreamingState,
235 +) -> list[StreamingResponsesResponse]:
236 + """Emit events for function call argument deltas."""
237 + events: list[StreamingResponsesResponse] = []
238 + if state.is_first_function_call_delta is False:
239 + state.is_first_function_call_delta = True
240 + state.current_item_id = f"fc_{random_uuid()}"
241 + state.current_call_id = f"call_{random_uuid()}"
242 + tool_call_item = ResponseFunctionToolCall(
243 + name=function_name,
244 + type="function_call",
245 + id=state.current_item_id,
246 + call_id=state.current_call_id,
247 + arguments="",
248 + status="in_progress",
249 + )
250 + events.append(
251 + ResponseOutputItemAddedEvent(
252 + type="response.output_item.added",
253 + sequence_number=-1,
254 + output_index=state.current_output_index,
255 + item=tool_call_item,
256 + )
257 + )
258 + # Always emit the delta (including on first call)
259 + events.append(
260 + ResponseFunctionCallArgumentsDeltaEvent(
261 + item_id=state.current_item_id,
262 + delta=delta,
263 + output_index=state.current_output_index,
264 + sequence_number=-1,
265 + type="response.function_call_arguments.delta",
266 + )
267 + )
268 + return events
269 +
270 +
271 +def emit_mcp_delta_events(
272 + delta: str,
273 + state: StreamingState,
274 recipient: str,
275 ) -> list[StreamingResponsesResponse]:
276 """Emit events for MCP tool delta streaming."""
410 - server_label = TOOL_NAME_TO_MCP_SERVER_LABEL.get(recipient, recipient)
277 + name, server_label = _resolve_mcp_name_label(recipient)
278 events: list[StreamingResponsesResponse] = []
279 if not state.sent_output_item_added:
280 state.sent_output_item_added = True
@@ -420,7 +287,7 @@ def emit_mcp_tool_delta_events(
287 item=McpCall(
288 type="mcp_call",
289 id=state.current_item_id,
423 - name=recipient,
290 + name=name,
291 arguments="",
292 server_label=server_label,
293 status="in_progress",
@@ -441,15 +308,15 @@ def emit_mcp_tool_delta_events(
308 sequence_number=-1,
309 output_index=state.current_output_index,
310 item_id=state.current_item_id,
444 - delta=ctx.last_content_delta,
311 + delta=delta,
312 )
313 )
314 return events
315
316
317 def emit_code_interpreter_delta_events(
451 - ctx: StreamingHarmonyContext,
452 - state: HarmonyStreamingState,
318 + delta: str,
319 + state: StreamingState,
320 ) -> list[StreamingResponsesResponse]:
321 """Emit events for code interpreter delta streaming."""
322 events: list[StreamingResponsesResponse] = []
@@ -485,151 +352,274 @@ def emit_code_interpreter_delta_events(
352 sequence_number=-1,
353 output_index=state.current_output_index,
354 item_id=state.current_item_id,
488 - delta=ctx.last_content_delta,
355 + delta=delta,
356 )
357 )
358 return events
359
360
494 -def emit_mcp_prefix_delta_events(
495 - ctx: StreamingHarmonyContext,
496 - state: HarmonyStreamingState,
361 +# =====================================================================
362 +# Shared leaf helpers — done events
363 +# =====================================================================
364 +
365 +
366 +def emit_text_output_done_events(
367 + text: str,
368 + state: StreamingState,
369 ) -> list[StreamingResponsesResponse]:
498 - """Emit events for MCP prefix (mcp.*) delta streaming."""
370 + """Emit events when a final text output item completes."""
371 + text_content = ResponseOutputText(
372 + type="output_text",
373 + text=text,
374 + annotations=[],
375 + )
376 events: list[StreamingResponsesResponse] = []
500 - if not state.sent_output_item_added:
501 - state.sent_output_item_added = True
502 - state.current_item_id = f"mcp_{random_uuid()}"
503 - mcp_name = ctx.parser.current_recipient[len("mcp.") :]
504 -
505 - events.append(
506 - ResponseOutputItemAddedEvent(
507 - type="response.output_item.added",
508 - sequence_number=-1,
509 - output_index=state.current_output_index,
510 - item=McpCall(
511 - type="mcp_call",
512 - id=state.current_item_id,
513 - name=mcp_name,
514 - arguments="",
515 - server_label=mcp_name,
516 - status="in_progress",
517 - ),
518 - )
377 + events.append(
378 + ResponseTextDoneEvent(
379 + type="response.output_text.done",
380 + sequence_number=-1,
381 + output_index=state.current_output_index,
382 + content_index=state.current_content_index,
383 + text=text,
384 + logprobs=[],
385 + item_id=state.current_item_id,
386 )
520 - events.append(
521 - ResponseMcpCallInProgressEvent(
522 - type="response.mcp_call.in_progress",
523 - sequence_number=-1,
524 - output_index=state.current_output_index,
525 - item_id=state.current_item_id,
526 - )
387 + )
388 + events.append(
389 + ResponseContentPartDoneEvent(
390 + type="response.content_part.done",
391 + sequence_number=-1,
392 + item_id=state.current_item_id,
393 + output_index=state.current_output_index,
394 + content_index=state.current_content_index,
395 + part=text_content,
396 + )
397 + )
398 + events.append(
399 + ResponseOutputItemDoneEvent(
400 + type="response.output_item.done",
401 + sequence_number=-1,
402 + output_index=state.current_output_index,
403 + item=ResponseOutputMessage(
404 + id=state.current_item_id,
405 + type="message",
406 + role="assistant",
407 + content=[text_content],
408 + status="completed",
409 + ),
410 )
411 + )
412 + return events
413
414 +
415 +def emit_reasoning_done_events(
416 + text: str,
417 + state: StreamingState,
418 +) -> list[StreamingResponsesResponse]:
419 + """Emit events when a reasoning (analysis) item completes."""
420 + content = ResponseReasoningTextContent(
421 + text=text,
422 + type="reasoning_text",
423 + )
424 + reasoning_item = ResponseReasoningItem(
425 + type="reasoning",
426 + content=[content],
427 + status="completed",
428 + id=state.current_item_id,
429 + summary=[],
430 + )
431 + events: list[StreamingResponsesResponse] = []
432 events.append(
530 - ResponseMcpCallArgumentsDeltaEvent(
531 - type="response.mcp_call_arguments.delta",
433 + ResponseReasoningTextDoneEvent(
434 + type="response.reasoning_text.done",
435 + item_id=state.current_item_id,
436 sequence_number=-1,
437 output_index=state.current_output_index,
438 + content_index=state.current_content_index,
439 + text=text,
440 + )
441 + )
442 + events.append(
443 + ResponseReasoningPartDoneEvent(
444 + type="response.reasoning_part.done",
445 + sequence_number=-1,
446 item_id=state.current_item_id,
535 - delta=ctx.last_content_delta,
447 + output_index=state.current_output_index,
448 + content_index=state.current_content_index,
449 + part=content,
450 + )
451 + )
452 + events.append(
453 + ResponseOutputItemDoneEvent(
454 + type="response.output_item.done",
455 + sequence_number=-1,
456 + output_index=state.current_output_index,
457 + item=reasoning_item,
458 )
459 )
460 return events
461
462
541 -def emit_function_call_delta_events(
542 - ctx: StreamingHarmonyContext,
543 - state: HarmonyStreamingState,
463 +def emit_function_call_done_events(
464 + function_name: str,
465 + arguments: str,
466 + state: StreamingState,
467 ) -> list[StreamingResponsesResponse]:
545 - """Emit events for developer function calls on commentary channel."""
546 - if not (
547 - ctx.parser.current_channel == "commentary"
548 - and ctx.parser.current_recipient
549 - and ctx.parser.current_recipient.startswith("functions.")
550 - ):
551 - return []
552 -
468 + """Emit events when a function call completes."""
469 events: list[StreamingResponsesResponse] = []
554 - if state.is_first_function_call_delta is False:
555 - state.is_first_function_call_delta = True
556 - fc_name = ctx.parser.current_recipient[len("functions.") :]
557 - state.current_item_id = f"fc_{random_uuid()}"
558 - tool_call_item = ResponseFunctionToolCall(
559 - name=fc_name,
560 - type="function_call",
561 - id=state.current_item_id,
562 - call_id=f"call_{random_uuid()}",
563 - arguments="",
564 - status="in_progress",
470 + events.append(
471 + ResponseFunctionCallArgumentsDoneEvent(
472 + type="response.function_call_arguments.done",
473 + arguments=arguments,
474 + name=function_name,
475 + item_id=state.current_item_id,
476 + output_index=state.current_output_index,
477 + sequence_number=-1,
478 )
566 - events.append(
567 - ResponseOutputItemAddedEvent(
568 - type="response.output_item.added",
569 - sequence_number=-1,
570 - output_index=state.current_output_index,
571 - item=tool_call_item,
572 - )
479 + )
480 + function_call_item = ResponseFunctionToolCall(
481 + type="function_call",
482 + arguments=arguments,
483 + name=function_name,
484 + item_id=state.current_item_id,
485 + output_index=state.current_output_index,
486 + sequence_number=-1,
487 + call_id=state.current_call_id,
488 + status="completed",
489 + )
490 + events.append(
491 + ResponseOutputItemDoneEvent(
492 + type="response.output_item.done",
493 + sequence_number=-1,
494 + output_index=state.current_output_index,
495 + item=function_call_item,
496 )
574 - # Always emit the delta (including on first call)
497 + )
498 + return events
499 +
500 +
501 +def emit_mcp_completion_events(
502 + recipient: str,
503 + arguments: str,
504 + state: StreamingState,
505 +) -> list[StreamingResponsesResponse]:
506 + """Emit events when an MCP tool call completes."""
507 + name, server_label = _resolve_mcp_name_label(recipient)
508 + events: list[StreamingResponsesResponse] = []
509 events.append(
576 - ResponseFunctionCallArgumentsDeltaEvent(
510 + ResponseMcpCallArgumentsDoneEvent(
511 + type="response.mcp_call_arguments.done",
512 + arguments=arguments,
513 + name=name,
514 item_id=state.current_item_id,
578 - delta=ctx.last_content_delta,
515 output_index=state.current_output_index,
516 sequence_number=-1,
581 - type="response.function_call_arguments.delta",
517 + )
518 + )
519 + events.append(
520 + ResponseMcpCallCompletedEvent(
521 + type="response.mcp_call.completed",
522 + sequence_number=-1,
523 + output_index=state.current_output_index,
524 + item_id=state.current_item_id,
525 + )
526 + )
527 + events.append(
528 + ResponseOutputItemDoneEvent(
529 + type="response.output_item.done",
530 + sequence_number=-1,
531 + output_index=state.current_output_index,
532 + item=McpCall(
533 + type="mcp_call",
534 + arguments=arguments,
535 + name=name,
536 + id=state.current_item_id,
537 + server_label=server_label,
538 + status="completed",
539 + ),
540 )
541 )
542 return events
543
544
545 +# =====================================================================
546 +# Harmony-specific dispatchers
547 +# =====================================================================
548 +
549 +
550 def emit_content_delta_events(
551 ctx: StreamingHarmonyContext,
589 - state: HarmonyStreamingState,
552 + state: StreamingState,
553 ) -> list[StreamingResponsesResponse]:
591 - """Emit events for content delta streaming based on channel type."""
592 - if not ctx.last_content_delta:
554 + """Emit events for content delta streaming based on channel type.
555 +
556 + This is a Harmony-specific dispatcher that extracts values from the
557 + Harmony context and delegates to shared leaf helpers.
558 + """
559 + delta = ctx.last_content_delta
560 + if not delta:
561 return []
562
595 - if ctx.parser.current_channel == "final" and ctx.parser.current_recipient is None:
596 - return emit_final_channel_delta_events(ctx, state)
597 - elif (
598 - ctx.parser.current_channel == "analysis"
599 - and ctx.parser.current_recipient is None
600 - ):
601 - return emit_analysis_channel_delta_events(ctx, state)
563 + channel = ctx.parser.current_channel
564 + recipient = ctx.parser.current_recipient
565 +
566 + if channel == "final" and recipient is None:
567 + return emit_text_delta_events(delta, state)
568 + elif channel == "analysis" and recipient is None:
569 + return emit_reasoning_delta_events(delta, state)
570 # built-in tools will be triggered on the analysis channel
571 # However, occasionally built-in tools will
572 # still be output to commentary.
605 - elif (
606 - ctx.parser.current_channel == "commentary"
607 - or ctx.parser.current_channel == "analysis"
608 - ) and ctx.parser.current_recipient is not None:
609 - recipient = ctx.parser.current_recipient
610 - # Check for function calls first - they have their own event handling
573 + elif channel in ("commentary", "analysis") and recipient is not None:
574 if recipient.startswith("functions."):
612 - return emit_function_call_delta_events(ctx, state)
613 - if is_mcp_tool_by_namespace(recipient):
614 - return emit_mcp_tool_delta_events(ctx, state, recipient)
615 - else:
616 - return emit_code_interpreter_delta_events(ctx, state)
617 - elif (
618 - (
619 - ctx.parser.current_channel == "commentary"
620 - or ctx.parser.current_channel == "analysis"
621 - )
622 - and ctx.parser.current_recipient is not None
623 - and ctx.parser.current_recipient.startswith("mcp.")
624 - ):
625 - return emit_mcp_prefix_delta_events(ctx, state)
575 + function_name = recipient[len("functions.") :]
576 + return emit_function_call_delta_events(delta, function_name, state)
577 + elif recipient == "python":
578 + return emit_code_interpreter_delta_events(delta, state)
579 + elif recipient.startswith("mcp.") or is_mcp_tool_by_namespace(recipient):
580 + return emit_mcp_delta_events(delta, state, recipient)
581 +
582 + return []
583 +
584
585 +def emit_previous_item_done_events(
586 + previous_item: HarmonyMessage,
587 + state: StreamingState,
588 +) -> list[StreamingResponsesResponse]:
589 + """Emit done events for the previous item when expecting a new start.
590 +
591 + This is a Harmony-specific dispatcher that extracts values from the
592 + Harmony parser's message object and delegates to shared leaf helpers.
593 + """
594 + text = previous_item.content[0].text
595 + if previous_item.recipient is not None:
596 + # Deal with tool call
597 + if previous_item.recipient.startswith("functions."):
598 + function_name = previous_item.recipient[len("functions.") :]
599 + return emit_function_call_done_events(function_name, text, state)
600 + elif previous_item.recipient == "python":
601 + return emit_code_interpreter_completion_events(previous_item, state)
602 + elif (
603 + is_mcp_tool_by_namespace(previous_item.recipient)
604 + and state.current_item_id is not None
605 + and state.current_item_id.startswith("mcp_")
606 + ):
607 + return emit_mcp_completion_events(previous_item.recipient, text, state)
608 + elif previous_item.channel == "analysis":
609 + return emit_reasoning_done_events(text, state)
610 + elif previous_item.channel == "final":
611 + return emit_text_output_done_events(text, state)
612 return []
613
614
615 +# =====================================================================
616 +# Harmony-specific tool lifecycle helpers
617 +# =====================================================================
618 +
619 +
620 def emit_browser_tool_events(
631 - previous_item,
632 - state: HarmonyStreamingState,
621 + previous_item: HarmonyMessage,
622 + state: StreamingState,
623 ) -> list[StreamingResponsesResponse]:
624 """Emit events for browser tool calls (web search)."""
625 function_name = previous_item.recipient[len("browser.") :]
@@ -714,53 +704,9 @@ def emit_browser_tool_events(
704 return events
705
706
717 -def emit_mcp_tool_completion_events(
718 - previous_item,
719 - state: HarmonyStreamingState,
720 -) -> list[StreamingResponsesResponse]:
721 - """Emit events when an MCP tool completes during assistant action turn."""
722 - recipient = previous_item.recipient
723 - server_label = TOOL_NAME_TO_MCP_SERVER_LABEL.get(recipient, recipient)
724 - events: list[StreamingResponsesResponse] = []
725 - events.append(
726 - ResponseMcpCallArgumentsDoneEvent(
727 - type="response.mcp_call_arguments.done",
728 - sequence_number=-1,
729 - output_index=state.current_output_index,
730 - item_id=state.current_item_id,
731 - arguments=previous_item.content[0].text,
732 - name=recipient,
733 - )
734 - )
735 - events.append(
736 - ResponseMcpCallCompletedEvent(
737 - type="response.mcp_call.completed",
738 - sequence_number=-1,
739 - output_index=state.current_output_index,
740 - item_id=state.current_item_id,
741 - )
742 - )
743 - events.append(
744 - ResponseOutputItemDoneEvent(
745 - type="response.output_item.done",
746 - sequence_number=-1,
747 - output_index=state.current_output_index,
748 - item=McpCall(
749 - type="mcp_call",
750 - id=state.current_item_id,
751 - name=recipient,
752 - arguments=previous_item.content[0].text,
753 - server_label=server_label,
754 - status="completed",
755 - ),
756 - )
757 - )
758 - return events
759 -
760 -
707 def emit_code_interpreter_completion_events(
762 - previous_item,
763 - state: HarmonyStreamingState,
708 + previous_item: HarmonyMessage,
709 + state: StreamingState,
710 ) -> list[StreamingResponsesResponse]:
711 """Emit events when code interpreter completes."""
712 events: list[StreamingResponsesResponse] = []
@@ -807,52 +753,9 @@ def emit_code_interpreter_completion_events(
753 return events
754
755
810 -def emit_mcp_prefix_completion_events(
811 - previous_item,
812 - state: HarmonyStreamingState,
813 -) -> list[StreamingResponsesResponse]:
814 - """Emit events when an MCP prefix tool (mcp.*) completes."""
815 - mcp_name = previous_item.recipient[len("mcp.") :]
816 - events: list[StreamingResponsesResponse] = []
817 - events.append(
818 - ResponseMcpCallArgumentsDoneEvent(
819 - type="response.mcp_call_arguments.done",
820 - sequence_number=-1,
821 - output_index=state.current_output_index,
822 - item_id=state.current_item_id,
823 - arguments=previous_item.content[0].text,
824 - name=mcp_name,
825 - )
826 - )
827 - events.append(
828 - ResponseMcpCallCompletedEvent(
829 - type="response.mcp_call.completed",
830 - sequence_number=-1,
831 - output_index=state.current_output_index,
832 - item_id=state.current_item_id,
833 - )
834 - )
835 - events.append(
836 - ResponseOutputItemDoneEvent(
837 - type="response.output_item.done",
838 - sequence_number=-1,
839 - output_index=state.current_output_index,
840 - item=McpCall(
841 - type="mcp_call",
842 - id=state.current_item_id,
843 - name=mcp_name,
844 - arguments=previous_item.content[0].text,
845 - server_label=mcp_name,
846 - status="completed",
847 - ),
848 - )
849 - )
850 - return events
851 -
852 -
756 def emit_tool_action_events(
757 ctx: StreamingHarmonyContext,
855 - state: HarmonyStreamingState,
758 + state: StreamingState,
759 tool_server: ToolServer | None,
760 ) -> list[StreamingResponsesResponse]:
761 """Emit events for tool action turn."""
@@ -879,19 +782,13 @@ def emit_tool_action_events(
782 and state.sent_output_item_added
783 ):
784 recipient = previous_item.recipient
882 - # Handle MCP prefix tool completion first
883 - if recipient.startswith("mcp."):
884 - events.extend(emit_mcp_prefix_completion_events(previous_item, state))
885 - else:
886 - # Handle other MCP tool and code interpreter completion
887 - is_mcp_tool = is_mcp_tool_by_namespace(
888 - recipient
889 - ) and state.current_item_id.startswith("mcp_")
890 - if is_mcp_tool:
891 - events.extend(emit_mcp_tool_completion_events(previous_item, state))
892 - else:
893 - events.extend(
894 - emit_code_interpreter_completion_events(previous_item, state)
785 + if recipient == "python":
786 + events.extend(emit_code_interpreter_completion_events(previous_item, state))
787 + elif recipient.startswith("mcp.") or is_mcp_tool_by_namespace(recipient):
788 + events.extend(
789 + emit_mcp_completion_events(
790 + recipient, previous_item.content[0].text, state
791 )
792 + )
793
794 return events