@@ -65,14 +65,31 @@ import http.server
6565import json
6666import pathlib
6767import sys
68+ import time
6869
6970state_path = pathlib.Path(sys.argv[1])
7071
7172
7273class Handler(http.server.BaseHTTPRequestHandler):
7374 def do_POST(self):
7475 length = int(self.headers.get("Content-Length", "0"))
75- self.rfile.read(length)
76+ body = self.rfile.read(length)
77+ try:
78+ payload = json.loads(body.decode("utf-8"))
79+ except (UnicodeDecodeError, json.JSONDecodeError):
80+ payload = {}
81+ if isinstance(payload, dict) and payload.get("stream") is True:
82+ self.send_response(200)
83+ self.send_header("Content-Type", "text/event-stream")
84+ self.send_header("Connection", "close")
85+ self.end_headers()
86+ for index in range(3):
87+ self.wfile.write(f"data: chunk-{index}\n\n".encode("utf-8"))
88+ self.wfile.flush()
89+ time.sleep(0.4)
90+ self.wfile.write(b"data: [DONE]\n\n")
91+ self.wfile.flush()
92+ return
7693 attempts = int(state_path.read_text(encoding="utf-8")) if state_path.exists() else 0
7794 attempts += 1
7895 state_path.write_text(str(attempts), encoding="utf-8")
@@ -318,6 +335,59 @@ expect_status 200 \
318335 -H ' Content-Type: application/json' \
319336 --data ' {"model":"gpt-4.1-mini","metadata":{"openphone_task":"true","risk_flags":""},"input":[{"role":"user","content":[{"type":"input_text","text":"hello"}]}]}'
320337
338+ # Streaming: the broker must relay SSE chunks incrementally instead of
339+ # buffering the full upstream response before replying.
340+ python3 - << 'PY ' "$port" "$static_token"
341+ import http.client
342+ import json
343+ import sys
344+ import time
345+
346+ port, token = sys.argv[1:]
347+ body = json.dumps({
348+ "model": "gpt-4.1-mini",
349+ "stream": True,
350+ "metadata": {"openphone_task": "true", "risk_flags": ""},
351+ "input": [{"role": "user", "content": [{"type": "input_text", "text": "hi"}]}],
352+ })
353+ connection = http.client.HTTPConnection("127.0.0.1", int(port), timeout=30)
354+ connection.request(
355+ "POST",
356+ "/v1/responses",
357+ body=body,
358+ headers={
359+ "Authorization": f"Bearer {token}",
360+ "Content-Type": "application/json",
361+ "Accept": "text/event-stream",
362+ },
363+ )
364+ response = connection.getresponse()
365+ if response.status != 200:
366+ raise SystemExit(f"streaming request failed: HTTP {response.status}")
367+ content_type = response.headers.get("Content-Type", "")
368+ if not content_type.startswith("text/event-stream"):
369+ raise SystemExit(f"streaming response has wrong content-type: {content_type}")
370+
371+ chunks = []
372+ arrival_times = []
373+ while True:
374+ chunk = response.read1(65536)
375+ if not chunk:
376+ break
377+ chunks.append(chunk)
378+ arrival_times.append(time.monotonic())
379+ payload = b"".join(chunks).decode("utf-8")
380+ for expected in ("data: chunk-0", "data: chunk-1", "data: chunk-2", "data: [DONE]"):
381+ if expected not in payload:
382+ raise SystemExit(f"streaming response missing {expected!r}: {payload!r}")
383+ if len(arrival_times) < 2:
384+ raise SystemExit("streaming response arrived as a single buffered chunk")
385+ spread = arrival_times[-1] - arrival_times[0]
386+ if spread < 0.2:
387+ raise SystemExit(f"streaming chunks were not delivered incrementally (spread={spread:.3f}s)")
388+ print(f"streaming smoke case passed ({len(chunks)} chunks over {spread:.2f}s)")
389+ PY
390+
321391expect_status 429 \
322392 -X POST " http://127.0.0.1:$port /v1/responses" \
323393 -H " Authorization: Bearer $static_token " \
@@ -374,7 +444,7 @@ for event in events:
374444 if "body" in event or "request" in event or "response" in event:
375445 raise SystemExit(f"audit event contains body-like key: {event}")
376446proxied = [event for event in events if event.get("outcome") == "proxied"]
377- if not proxied or proxied[-1] .get("provider_attempts") != 2 :
447+ if not any(event .get("provider_attempts") == 2 for event in proxied) :
378448 raise SystemExit("broker did not record retried provider forwarding")
379449PY
380450
0 commit comments