Conversation
- Enable ManualEpDownload, call ensureEpsDownloaded with progress callback - Render per-EP progress bars in terminal Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rogress - Update JS/C#/Rust/Python SDKs: ensure_eps_downloaded -> download_and_register_eps - Add progress callback support to Python SDK ensure_eps_downloaded() - Python now parses 'epName|percent' streaming chunks like other SDKs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add discoverEps() to JS, C#, Rust, and Python SDKs (wraps discover_eps command) - Add optional names parameter to ensureEpsDownloaded() in all SDKs - Update JS sample to demonstrate: discover EPs -> download all with progress - Update C# HelloFoundryLocalSdk sample with discover + selective download - Add EpInfo type to C# and Rust SDKs Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Track current EP name; emit newline when switching to preserve the previous EP's completed progress bar. Each EP gets its own line. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pad EP names to the longest name so (registered:) and progress bars start at the same column across all lines. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Point all SDKs and samples at the latest FoundryLocalCore packages published to ORT-Nightly, which include per-EP progress callback support with Action<string, double> on IEpBootstrapper. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…a768b6a - Streamline Program.cs: remove verbose comments and redundant code - Update NuGet versions to 0.9.0-dev-20260331T004032-a768b6a - Update JS SDK versions (standard + WinML) - Update Rust CORE_VERSION - Update Python requirements to 0.9.0.dev20260331004032 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- C#: wrap EP logic in eps.Length > 0 guard to avoid InvalidOperationException - JS: wrap EP logic in eps.length > 0 guard to avoid Math.max(-Infinity) - Replace nullable currentEp with empty string for cleaner null safety Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…gitignore - Restore all original comments and code structure in C# and JS samples - Only change EP-related code (discover + per-EP progress download) - Guard empty EP arrays to avoid InvalidOperationException / Math.max(-Infinity) - Remove package.json from tracking (not needed in repo) - Remove copilot-instructions.md from gitignore Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- C# README: EP discovery and progress section, EpInfo in API ref table - C# API docs: regenerated with xmldoc2md (new DiscoverEps, EnsureEpsDownloadedAsync overload, EpInfo) - JS README: EP discovery and progress section in WinML section - JS API docs: regenerated with typedoc (new discoverEps, ensureEpsDownloaded) - Python README: EP discovery feature and usage section - Rust README: EP discovery and progress section - Fix AOT compat: use source-generated JsonSerializationContext for EpInfo[] deserialization - copilot-instructions.md: add documentation checklist reminder Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Adds per-execution-provider (EP) discovery and per-EP download progress reporting across C#, JS/TS, Python, and Rust SDKs, plus updated samples and package versions to a newer Core build.
Changes:
- Added EP discovery APIs (
discover_epswrappers) andEpInfotypes (C#/Rust) or equivalents (JS/Python). - Added per-EP progress callbacks for EP download/registration, using the
"epName|percent"streaming wire format. - Updated Core package versions and refreshed SDK/sample documentation and examples.
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| sdk/rust/src/types.rs | Adds EpInfo struct to model EP discovery results. |
| sdk/rust/src/foundry_local_manager.rs | Adds discover_eps and ensure_eps_downloaded APIs (with optional streaming progress). |
| sdk/rust/build.rs | Bumps CORE_VERSION to a new dev build. |
| sdk/rust/README.md | Documents EP discovery + per-EP progress usage. |
| sdk/python/src/foundry_local_manager.py | Adds discover_eps() and extends ensure_eps_downloaded() with names + progress callback. |
| sdk/python/requirements.txt | Updates foundry-local-core pinned dev version. |
| sdk/python/README.md | Adds usage docs for EP discovery and per-EP progress. |
| sdk/js/src/foundryLocalManager.ts | Adds discoverEps() and ensureEpsDownloaded() with streaming progress parsing. |
| sdk/js/script/install-winml.cjs | Bumps Core.WinML package version used by install script. |
| sdk/js/script/install-standard.cjs | Bumps Core package version used by install script. |
| sdk/js/docs/classes/FoundryLocalManager.md | Adds API docs for discoverEps / ensureEpsDownloaded. |
| sdk/js/README.md | Documents EP discovery + per-EP progress usage. |
| sdk/cs/src/FoundryLocalManager.cs | Adds DiscoverEps() and overloads EnsureEpsDownloadedAsync with names + per-EP progress callback. |
| sdk/cs/src/EpInfo.cs | Introduces EpInfo model for C# EP discovery. |
| sdk/cs/src/Detail/JsonSerializationContext.cs | Adds source-gen serialization support for EpInfo[]. |
| sdk/cs/docs/api/microsoft.ai.foundry.local.openaichatclient.md | Updates generated API docs (includes new overload docs). |
| sdk/cs/docs/api/microsoft.ai.foundry.local.openai.responseformatextended.md | Adds generated API doc page for ResponseFormatExtended. |
| sdk/cs/docs/api/microsoft.ai.foundry.local.modelinfo.md | Updates generated docs for newly documented ModelInfo properties. |
| sdk/cs/docs/api/microsoft.ai.foundry.local.foundrylocalmanager.md | Updates generated docs for DiscoverEps and new EnsureEpsDownloadedAsync signature. |
| sdk/cs/docs/api/microsoft.ai.foundry.local.epinfo.md | Adds generated API doc page for EpInfo. |
| sdk/cs/docs/api/index.md | Adds EpInfo and ResponseFormatExtended entries to API index. |
| sdk/cs/README.md | Adds usage docs for EP discovery + per-EP progress and updates key types table. |
| samples/js/native-chat-completions/app.js | Updates sample to discover EPs and render per-EP progress bars. |
| samples/cs/GettingStarted/src/HelloFoundryLocalSdk/Program.cs | Updates sample to discover EPs and show per-EP progress during downloads. |
| samples/cs/GettingStarted/Directory.Packages.props | Updates Foundry.Local and WinML package versions. |
| .github/copilot-instructions.md | Adds repo guidance on EP progress format and package update workflow. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
sdk/js/src/foundryLocalManager.ts
Outdated
| * Discovers the execution providers available for download and registration. | ||
| * @returns An array of EP info objects with name and registration status. | ||
| */ | ||
| public discoverEps(): { name: string; isRegistered: boolean }[] { |
There was a problem hiding this comment.
The declared return type is { name, isRegistered }[], but the implementation returns the Core JSON as-is (which elsewhere in this PR is documented/used as { Name, IsRegistered }). This will cause runtime undefined values for consumers who follow the TypeScript type (ep.name) and can also break generated docs/examples consistency. Fix by either (a) changing the return type to match the actual JSON shape ({ Name: string; IsRegistered: boolean }[]) or (b) mapping the parsed objects to a camelCase shape before returning (and then update docs/samples accordingly).
| public discoverEps(): { name: string; isRegistered: boolean }[] { | |
| public discoverEps(): { Name: string; IsRegistered: boolean }[] { |
| const eps = manager.discoverEps(); | ||
| eps.forEach(ep => { | ||
| console.log(` ${ep.Name} (registered: ${ep.IsRegistered})`); | ||
| }); | ||
|
|
||
| // Download with per-EP progress reporting | ||
| const epNames = eps.map(ep => ep.Name); |
There was a problem hiding this comment.
The README examples/documentation treat discoverEps() results as { Name, IsRegistered }, but the TS signature currently declares { name, isRegistered }. Once the API shape is finalized, please align the README with the actual exported JS/TS API surface (and keep it consistent with the generated docs in sdk/js/docs/...).
| }); | ||
| ``` | ||
|
|
||
| `discoverEps()` returns an array of `{ Name, IsRegistered }` objects. The progress callback receives the EP name and a percentage (0–100) as each EP downloads. |
There was a problem hiding this comment.
The README examples/documentation treat discoverEps() results as { Name, IsRegistered }, but the TS signature currently declares { name, isRegistered }. Once the API shape is finalized, please align the README with the actual exported JS/TS API surface (and keep it consistent with the generated docs in sdk/js/docs/...).
| let ep_names: Vec<&str> = eps.iter().map(|ep| ep.name.as_str()).collect(); | ||
| manager.ensure_eps_downloaded( | ||
| Some(&ep_names), | ||
| Some(|name: &str, percent: f64| { | ||
| print!("\r {name}: {percent:.1}%"); | ||
| std::io::Write::flush(&mut std::io::stdout()).ok(); | ||
| }), | ||
| ).await?; |
There was a problem hiding this comment.
This example won't compile against the Rust API added in this PR: ensure_eps_downloaded currently accepts Option<F> where F: FnMut(&str), documented as receiving raw \"name|percent\" chunks. Update the README to show parsing the raw chunk inside the callback, or change the Rust API to accept a typed callback (e.g., (name, percent)), and keep the README aligned with that choice.
| /// If `progress` is provided, it receives raw `"name|percent"` strings for each | ||
| /// EP as the download proceeds. | ||
| pub async fn ensure_eps_downloaded<F>( | ||
| &self, | ||
| names: Option<&[&str]>, | ||
| progress: Option<F>, | ||
| ) -> Result<()> | ||
| where | ||
| F: FnMut(&str) + Send + 'static, | ||
| { | ||
| let params = names.map(|n| json!({ "Names": n.join(",") })); | ||
| match progress { | ||
| Some(cb) => { | ||
| self.core | ||
| .execute_command_streaming_async( | ||
| "download_and_register_eps".into(), | ||
| params.as_ref(), | ||
| cb, |
There was a problem hiding this comment.
Other SDKs in this PR expose a per-EP progress callback as (ep_name, percent) while Rust exposes raw wire-format chunks. Consider parsing the \"name|percent\" format inside the Rust SDK and surfacing a typed callback (e.g., FnMut(Option<&str>, f64) or FnMut(&str, f64)), which makes the API consistent across languages and avoids duplicating parsing logic in every consumer. If you keep the raw format, the public docs/examples should consistently describe and demonstrate the raw chunk contract.
| /// If `progress` is provided, it receives raw `"name|percent"` strings for each | |
| /// EP as the download proceeds. | |
| pub async fn ensure_eps_downloaded<F>( | |
| &self, | |
| names: Option<&[&str]>, | |
| progress: Option<F>, | |
| ) -> Result<()> | |
| where | |
| F: FnMut(&str) + Send + 'static, | |
| { | |
| let params = names.map(|n| json!({ "Names": n.join(",") })); | |
| match progress { | |
| Some(cb) => { | |
| self.core | |
| .execute_command_streaming_async( | |
| "download_and_register_eps".into(), | |
| params.as_ref(), | |
| cb, | |
| /// If `progress` is provided, it receives parsed `(ep_name, percent)` updates for each | |
| /// EP as the download proceeds. `ep_name` is `None` if the name is not present. | |
| pub async fn ensure_eps_downloaded<F>( | |
| &self, | |
| names: Option<&[&str]>, | |
| progress: Option<F>, | |
| ) -> Result<()> | |
| where | |
| F: FnMut(Option<&str>, f64) + Send + 'static, | |
| { | |
| let params = names.map(|n| json!({ "Names": n.join(",") })); | |
| match progress { | |
| Some(mut user_cb) => { | |
| self.core | |
| .execute_command_streaming_async( | |
| "download_and_register_eps".into(), | |
| params.as_ref(), | |
| move |chunk: &str| { | |
| if let Some((name_part, percent_part)) = chunk.split_once('|') { | |
| if let Ok(percent) = percent_part.trim().parse::<f64>() { | |
| let ep_name = if name_part.is_empty() { | |
| None | |
| } else { | |
| Some(name_part) | |
| }; | |
| user_cb(ep_name, percent); | |
| } | |
| } | |
| }, |
sdk/cs/src/FoundryLocalManager.cs
Outdated
| if (double.TryParse(progressString[(sepIndex + 1)..], out var percent)) | ||
| { | ||
| progressCallback(string.IsNullOrEmpty(name) ? null : name, percent); | ||
| } |
There was a problem hiding this comment.
Parsing percent via double.TryParse(...) is culture-sensitive; on locales that use , for decimals, valid wire values like \"42.5\" may fail to parse and suppress progress updates. Use double.TryParse(..., NumberStyles.Float, CultureInfo.InvariantCulture, out var percent) (and add the corresponding using directives) so the callback works reliably regardless of current culture.
|
|
||
| ```csharp | ||
| public Task EnsureEpsDownloadedAsync(Nullable<CancellationToken> ct) | ||
| public Task EnsureEpsDownloadedAsync(IEnumerable<string> names, Action<string, double> progressCallback, Nullable<CancellationToken> ct) |
There was a problem hiding this comment.
The generated API docs show non-optional names and progressCallback parameters, but the implementation signature in FoundryLocalManager.cs makes both optional (IEnumerable<string>? names = null, Action<string?, double>? progressCallback = null). Please regenerate/fix the docs so they accurately reflect optionality/nullability; otherwise consumers may treat optional parameters as required and miss the null name possibility.
| `names` [IEnumerable<String>](https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.ienumerable-1)<br> | ||
| Optional list of EP names to download. If null or empty, all discoverable EPs are downloaded. | ||
|
|
||
| `progressCallback` [Action<String, Double>](https://docs.microsoft.com/en-us/dotnet/api/system.action-2)<br> | ||
| Optional callback receiving per-EP progress updates. |
There was a problem hiding this comment.
The generated API docs show non-optional names and progressCallback parameters, but the implementation signature in FoundryLocalManager.cs makes both optional (IEnumerable<string>? names = null, Action<string?, double>? progressCallback = null). Please regenerate/fix the docs so they accurately reflect optionality/nullability; otherwise consumers may treat optional parameters as required and miss the null name possibility.
| def _on_chunk(chunk: str): | ||
| sep_index = chunk.find('|') | ||
| if sep_index >= 0: | ||
| name = chunk[:sep_index] or None | ||
| try: | ||
| percent = float(chunk[sep_index + 1:]) | ||
| except ValueError: | ||
| percent = 0.0 | ||
| progress_callback(name, percent) |
There was a problem hiding this comment.
progress_callback is documented as receiving (ep_name: str, percent: float), but this implementation may pass None for ep_name. Either always pass a string (e.g., empty string) or update the docstring/type hints to indicate Optional[str] so the callback contract is consistent and predictable for typed users.
- JS: discoverEps() return type changed to { Name, IsRegistered } matching Core JSON
- Rust: parse wire format inside SDK; expose typed FnMut(&str, f64) callback
- C#: use CultureInfo.InvariantCulture for double.TryParse (locale safety)
- Python: pass empty string instead of None for ep_name in callback
- Regenerated JS (typedoc) and C# (xmldoc2md) API docs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
execute_command_streaming_async and execute_command_async take Option<Value>, not Option<&Value>. Remove .as_ref() calls. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2527a22 to
dae5ca8
Compare
994625d to
dae5ca8
Compare
Adds per-execution-provider download progress reporting across all SDKs and samples. Previously, EP downloads
only reported aggregate progress (e.g., "1 of 4 done"). Now each EP reports individual download progress
(0–100%), enabling real-time progress bars per EP.
Changes
SDK — C# (sdk/cs/)
per-EP progress. Parses "epName|percent" wire format from native interop.
SDK — JavaScript (sdk/js/)
SDK — Python (sdk/python/)
SDK — Rust (sdk/rust/)
Samples
progress download. Guarded for empty EP arrays.
and progress bars. Guarded for empty EP arrays.