Summary
OpenAIChatClient wraps streamed Responses API events in #untilTerminalEvent (packages/openai/src/chat-client.ts), which stops pulling the SSE stream as soon as a terminal event (response.completed / response.incomplete / response.failed / response.cancelled) has been yielded and releases the underlying SDK stream, instead of draining to the connection close the way the OpenAI SDK's own iterator does.
This is a deliberate workaround for a service-side behavior of Azure AI Foundry's OpenAI-compatible endpoint, not a permanent design choice. This issue tracks its removal once the service is fixed.
The service behavior it works around
Foundry's {project-endpoint}/openai/v1/responses (measured 2026-08-09):
- holds the HTTP connection open for ~5.0 seconds after sending the terminal SSE event (tail measured at 5008±4ms, identical across
Accept-Encoding: identity / gzip / default), and
- never sends the
data: [DONE] sentinel.
The OpenAI platform sends [DONE] and closes immediately after the terminal event. Official SDKs (openai-node, openai-python, openai-dotnet, openai-go) iterate SSE streams until [DONE] or connection close, so against Foundry every streamed request pays the fixed ~5s idle tail after response.completed. For a hosted agent, whose hosting layer streams every model call, that is ~5s of avoidable latency per model round.
Repro:
curl -sS -N -X POST "$FOUNDRY_PROJECT_ENDPOINT/openai/v1/responses" \
-H "authorization: Bearer $(az account get-access-token --scope https://ai.azure.com/.default --query accessToken -o tsv)" \
-H "content-type: application/json" \
-d '{"model":"<deployment>","input":"Reply with exactly the word OK.","store":false,"stream":true}'
response.completed arrives in ~1.1–1.6s; the connection stays open for another ~5s with no further data.
Why the workaround is safe to keep in the meantime
The Responses protocol defines no events after the terminal one. Against a well-behaved endpoint the wrapper is a no-op: the close follows the terminal event at once, so stopping there changes nothing.
Removal steps (when Foundry ends streams promptly after the terminal event)
- Delete
#untilTerminalEvent in packages/openai/src/chat-client.ts and pass the SDK stream straight to #parseStream at its two call sites (the create-stream path and the background-resume path).
- Delete the paired test
releases the SSE stream at the terminal event instead of draining to the connection close in packages/openai/src/chat-client.test.ts.
- Re-run the repro above and confirm the stream now ends promptly after the terminal event.
Feedback about the service behavior has been submitted to the Azure AI Foundry team.
Summary
OpenAIChatClientwraps streamed Responses API events in#untilTerminalEvent(packages/openai/src/chat-client.ts), which stops pulling the SSE stream as soon as a terminal event (response.completed/response.incomplete/response.failed/response.cancelled) has been yielded and releases the underlying SDK stream, instead of draining to the connection close the way the OpenAI SDK's own iterator does.This is a deliberate workaround for a service-side behavior of Azure AI Foundry's OpenAI-compatible endpoint, not a permanent design choice. This issue tracks its removal once the service is fixed.
The service behavior it works around
Foundry's
{project-endpoint}/openai/v1/responses(measured 2026-08-09):Accept-Encoding: identity/gzip/ default), anddata: [DONE]sentinel.The OpenAI platform sends
[DONE]and closes immediately after the terminal event. Official SDKs (openai-node, openai-python, openai-dotnet, openai-go) iterate SSE streams until[DONE]or connection close, so against Foundry every streamed request pays the fixed ~5s idle tail afterresponse.completed. For a hosted agent, whose hosting layer streams every model call, that is ~5s of avoidable latency per model round.Repro:
response.completedarrives in ~1.1–1.6s; the connection stays open for another ~5s with no further data.Why the workaround is safe to keep in the meantime
The Responses protocol defines no events after the terminal one. Against a well-behaved endpoint the wrapper is a no-op: the close follows the terminal event at once, so stopping there changes nothing.
Removal steps (when Foundry ends streams promptly after the terminal event)
#untilTerminalEventinpackages/openai/src/chat-client.tsand pass the SDK stream straight to#parseStreamat its two call sites (the create-stream path and the background-resume path).releases the SSE stream at the terminal event instead of draining to the connection closeinpackages/openai/src/chat-client.test.ts.Feedback about the service behavior has been submitted to the Azure AI Foundry team.