Convert JS SDK streaming APIs from callbacks to async iterables#545
Convert JS SDK streaming APIs from callbacks to async iterables#545
Conversation
…pattern Addresses #507 - ChatClient.completeStreamingChat() and AudioClient.transcribeStreaming() now return AsyncIterable<any> instead of accepting callback functions, enabling the standard `for await (const chunk of ...)` pattern. BREAKING CHANGE: completeStreamingChat() and transcribeStreaming() no longer accept callback parameters. Use `for await` iteration. Co-authored-by: baijumeswani <12852605+baijumeswani@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/Foundry-Local/sessions/f8227df8-7016-4fe1-9884-56bbdcf326b7
Co-authored-by: baijumeswani <12852605+baijumeswani@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/Foundry-Local/sessions/f8227df8-7016-4fe1-9884-56bbdcf326b7
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@copilot do you need to make similar changes to rust sdk as well? |
No — the Rust SDK already returns stream types ( |
There was a problem hiding this comment.
Pull request overview
This PR updates the JS SDK streaming APIs to follow the common for await (...) pattern by removing callback-based overloads and returning AsyncIterable streams for chat completions and audio transcription.
Changes:
- Refactors
ChatClient.completeStreamingChat()andAudioClient.transcribeStreaming()to returnAsyncIterable<any>instead of accepting callbacks. - Updates streaming tests and examples to iterate with
for await (...). - Regenerates/updates TypeDoc + READMEs to reflect the new streaming API surface.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| sdk/js/src/openai/chatClient.ts | Converts streaming chat completion from callback-based API to AsyncIterable adapter. |
| sdk/js/src/openai/audioClient.ts | Converts streaming transcription from callback-based API to AsyncIterable adapter. |
| sdk/js/test/openai/chatClient.test.ts | Migrates streaming chat tests to for await iteration and removes callback validation tests. |
| sdk/js/test/openai/audioClient.test.ts | Migrates streaming audio tests to for await iteration and removes callback validation tests. |
| sdk/js/examples/chat-completion.ts | Updates streaming example to for await usage. |
| sdk/js/examples/tool-calling.ts | Updates streaming tool-calling example to for await usage. |
| sdk/js/examples/audio-transcription.ts | Updates streaming transcription example to for await usage. |
| sdk/js/README.md | Updates SDK README examples to use async iteration for streaming. |
| README.md | Updates root README streaming transcription snippet to async iteration. |
| samples/js/native-chat-completions/app.js | Updates sample streaming chat usage to async iteration. |
| samples/js/chat-and-audio-foundry-local/src/app.js | Updates sample streaming summary generation to async iteration. |
| samples/js/audio-transcription-example/app.js | Updates sample streaming transcription usage to async iteration. |
| sdk/js/docs/classes/ChatClient.md | Updates generated API docs for new AsyncIterable streaming signature + example. |
| sdk/js/docs/classes/AudioClient.md | Updates generated API docs for new AsyncIterable streaming signature + example. |
| sdk/js/docs/classes/ChatClientSettings.md | Regenerated docs reflecting optional property markers. |
| sdk/js/docs/classes/AudioClientSettings.md | Regenerated docs reflecting optional property markers. |
| sdk/js/docs/classes/ResponsesClientSettings.md | Regenerated docs reflecting optional property markers. |
| sdk/js/docs/classes/Model.md | Updates generated throws description text. |
| sdk/js/docs/README.md | Regenerated docs reflecting optional property markers and signatures. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…dation tests - Add return() method to async iterators in chatClient.ts and audioClient.ts to prevent unbounded buffering when consumers break out of for-await early - Add cancelled/error guards in native streaming callbacks to skip work after error or cancellation - Fix validation tests to assert synchronous throws directly instead of wrapping in unreachable for-await loops Co-authored-by: baijumeswani <12852605+baijumeswani@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/Foundry-Local/sessions/69aaf402-f4bd-496b-9d37-e139a3223cdb
|
Addressed all 6 items from the review in commit
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 19 out of 19 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…tion docs, tools example - Replace O(n) chunks.shift() with head-index + compaction for O(1) dequeue - Add nextInFlight guard to throw on concurrent next() calls - Add comment in return() explaining native stream cancellation limitation - Update completeStreamingChat JSDoc example to show both overloads - Regenerate TypeDoc API docs Co-authored-by: baijumeswani <12852605+baijumeswani@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/Foundry-Local/sessions/dfa24137-7f37-4aa6-adb9-a735b7ce4878
|
All 7 items addressed in
|
| // Don't throw from callback - store first error and stop processing | ||
| error = new Error(`Failed to parse streaming chunk: ${e instanceof Error ? e.message : String(e)}`, { cause: e }); | ||
| return; | ||
| const coreInterop = this.coreInterop; |
There was a problem hiding this comment.
is this necessary? can we just use this.coreInterop on L#130?
There was a problem hiding this comment.
Yes, the local capture is necessary. The [Symbol.asyncIterator]() method on line 123 is a regular method on the returned object literal, so this inside it refers to that object — not the AudioClient instance. Added a comment explaining this in d39bad8.
| error = new Error(`Failed to parse streaming chunk: ${e instanceof Error ? e.message : String(e)}`, { cause: e }); | ||
| return; | ||
| const coreInterop = this.coreInterop; | ||
| const modelId = this.modelId; |
There was a problem hiding this comment.
same question as for coreInterop
There was a problem hiding this comment.
Same reason as coreInterop — this is unavailable inside the [Symbol.asyncIterator]() closure. Comment added in d39bad8.
sdk/js/src/openai/audioClient.ts
Outdated
| if (resolve) { | ||
| const r = resolve; | ||
| resolve = null; | ||
| r(); |
There was a problem hiding this comment.
what does this do? can we include a comment?
There was a problem hiding this comment.
It resolves the promise that a pending next() call is awaiting, waking it up so it can see that the stream has ended (done = true). Added clarifying comments in d39bad8.
| } | ||
| }); | ||
|
|
||
| return { |
There was a problem hiding this comment.
is this a nested return statement? kind of unintuitive. please add a comment why this is designed this way.
There was a problem hiding this comment.
This is the standard AsyncIterable/AsyncIterator two-level factory pattern: [Symbol.asyncIterator]() returns the iterator object (with next() and return() methods) that for await consumes. Added a structural comment explaining this in d39bad8.
| const tools = Array.isArray(toolsOrCallback) ? toolsOrCallback : undefined; | ||
| const callback = (Array.isArray(toolsOrCallback) ? maybeCallback : toolsOrCallback) as ((chunk: any) => void) | undefined; | ||
|
|
||
| public completeStreamingChat(messages: any[]): AsyncIterable<any>; |
There was a problem hiding this comment.
same general comments as in audioClient
There was a problem hiding this comment.
Applied the same comments to chatClient.ts in d39bad8.
…tern, and iterator structure Address @prathikr review feedback: - Explain why coreInterop/modelId are captured to local variables (this is not available inside [Symbol.asyncIterator]() on the returned object) - Explain the AsyncIterable/AsyncIterator factory pattern with structural comments - Add comments on .then() handler explaining the promise-resolve wake-up pattern - Apply same comments to both audioClient.ts and chatClient.ts Co-authored-by: baijumeswani <12852605+baijumeswani@users.noreply.github.com> Agent-Logs-Url: https://github.com/microsoft/Foundry-Local/sessions/b62befe1-e407-4668-82a1-a2a22267eed9
Addressed all 5 comments from @prathikr in
|
return()hook to async iterators to prevent unbounded buffering on early breakchunks.shift()with O(1) head-index dequeue with compactionnext()calls withnextInFlightflagreturn()completeStreamingChat(messages, tools)overload to passtoolsthis).then()handler⚡ Quickly spin up Copilot coding agent tasks from anywhere on your macOS or Windows machine with Raycast.