Skip to content

[AI] Windows Copilot Runtime (Phi Silica) support#178

Draft
mattleibow wants to merge 8 commits into
mainfrom
mattleibow/ai-windows-phi-silica
Draft

[AI] Windows Copilot Runtime (Phi Silica) support#178
mattleibow wants to merge 8 commits into
mainfrom
mattleibow/ai-windows-phi-silica

Conversation

@mattleibow
Copy link
Copy Markdown
Member

@mattleibow mattleibow commented Apr 28, 2026

Summary

Stacked on top of #171. Migrates Windows Phi Silica (Copilot Runtime) support from dotnet/maui#33605 into maui-labs.

Changes

Library (src/AI/Microsoft.Maui.Essentials.AI)

  • Add PhiSilicaChatClient, PhiSilicaExtensions, PhiSilicaModelFactory (verbatim copy)
  • Add VersionOverride=2.0.0-experimental6 for Microsoft.WindowsAppSDK (AI projects only — does not affect other repo consumers)
  • Add AppxOSMaxVersionTestedReplaceManifestVersion=false to prevent SDK from overriding the manifest's MaxVersionTested
  • Update PublicAPI/net-windows/PublicAPI.Unshipped.txt with 7 new API entries

Sample

  • Add AppContentIndexerSearchService (Windows semantic search via OS AppContentIndexer)
  • Add PhiSilicaToolsAndSchemaClient (function-calling middleware for Phi Silica)
  • Register AddPhiSilicaServices in MauiProgram.cs via #elif WINDOWS
  • Update Windows Package.appxmanifest: systemAIModels capability + MaxVersionTested=10.0.26226.0

DeviceTests

  • Add PhiSilicaChatClientTests and PhiSilicaExperimentTests (verbatim, [Category] stripped)
  • Mark 3 base test methods virtual so Phi Silica subclass can override them
  • Link PhiSilicaToolsAndSchemaClient from sample (file link, Windows only)
  • Add SelfContained + WindowsAppSDKSelfContained for Windows to bundle experimental WinAppSDK into MSIX

Docs

  • Move TOOL-CALLING-README.md to docs/ai/ (was in Platform/Windows in source)

Notes

  • Microsoft.WindowsAppSDK 2.0.0-experimental6 is required for Microsoft.Windows.AI.Text.LanguageModel (Phi Silica WinRT API). The VersionOverride scopes this to AI projects only.
  • Phi Silica requires Windows 10.0.26100.0 at runtime ([SupportedOSPlatform]); TFM stays at 19041 for build compat.
  • After PR Add Microsoft.Maui.Essentials.AI — on-device AI for .NET MAUI #171 merges, GitHub will automatically retarget this PR to main.

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expert Code Review — PR #178: Windows Copilot Runtime (Phi Silica) Support

Reviewed all changed files across the library, sample, and device tests. Below are the findings ordered by severity.


🔴 CRITICAL

1. DI crash on Windows: ISemanticSearchService resolution throws at runtime

  • File: samples/Microsoft.Maui.Essentials.AI.Sample/MauiProgram.cs
  • Line: 70–71
  • Scenario: On Windows, AddPhiSilicaServices() registers ISemanticSearchServiceAppContentIndexerSearchService (line 205). Then line 70 unconditionally overwrites it with a factory that resolves IEmbeddingGenerator<string, Embedding<float>> — which is never registered in the Windows path. The last registration wins in DI, so any consumer of ISemanticSearchService will throw InvalidOperationException at runtime.
  • Recommendation: Wrap lines 69–71 in #if !WINDOWS (or move the registration into each platform method that actually registers an embedding generator), so on Windows only AppContentIndexerSearchService is used.

🟡 MODERATE

2. Negative TopK silently wraps to huge uint — unexpected model behavior

  • File: src/AI/Microsoft.Maui.Essentials.AI/Platform/Windows/PhiSilicaChatClient.cs
  • Line: 249
  • Scenario: ChatOptions.TopK is int. If a consumer passes a negative value (e.g., -1 as sentinel), (uint)topK wraps to 4294967295, causing the model to behave unpredictably.
  • Recommendation: Add a guard:
    if (topK < 0)
        throw new ArgumentOutOfRangeException(nameof(options), "TopK must be non-negative.");
    languageModelOptions.TopK = (uint)topK;

3. CancellationTokenRegistration not disposed — potential memory leak with long-lived tokens

  • File: src/AI/Microsoft.Maui.Essentials.AI/Platform/Windows/PhiSilicaChatClient.cs
  • Line: 122
  • Scenario: cancellationToken.Register(...) returns a CancellationTokenRegistration. If the caller uses a long-lived or linked token, the registration (and its closure capturing operation) stays alive until the token is disposed. For short-lived per-request tokens this is fine; for singleton tokens it leaks.
  • Recommendation: Capture the registration and dispose it after the enumeration completes:
    var ctr = cancellationToken.Register(() => operation.Cancel());
    try { await foreach (...) { yield return update; } }
    finally { ctr.Dispose(); }

4. Thread-unsafe Dictionary access in AppContentIndexerSearchService

  • File: samples/Microsoft.Maui.Essentials.AI.Sample/Services/AppContentIndexerSearchService.cs
  • Lines: 15–30, 34, 44
  • Scenario: GetOrCreateIndexer reads/writes a plain Dictionary<string, AppContentIndexer> from multiple Task.Run callbacks concurrently (both IndexAsync and SearchAsync call it from pool threads). Concurrent access to Dictionary causes corruption or KeyNotFoundException.
  • Recommendation: Use ConcurrentDictionary<string, AppContentIndexer> with GetOrAdd, or add a lock around the method.

5. PhiSilicaExtensions.AsIChatClient missing [SupportedOSPlatform] attribute

  • File: src/AI/Microsoft.Maui.Essentials.AI/Platform/Windows/PhiSilicaExtensions.cs
  • Lines: 10, 23
  • Scenario: PhiSilicaChatClient is annotated with [SupportedOSPlatform("windows10.0.26100.0")], but the extension method AsIChatClient (and its class) has no such attribute. Consumers calling model.AsIChatClient() won't get platform compatibility warnings at their call-site.
  • Recommendation: Add [SupportedOSPlatform("windows10.0.26100.0")] to the class or method.

🟢 MINOR

6. JsonDocument not disposed in BuildToolCallSchema

  • File: samples/Microsoft.Maui.Essentials.AI.Sample/Services/PhiSilicaToolsAndSchemaClient.cs
  • Lines: 271, 293
  • Scenario: JsonDocument.Parse(json).RootElement.Clone() creates a JsonDocument that is never disposed. While .Clone() makes the element independent and the GC will clean up eventually, it pins pooled buffers until finalization.
  • Recommendation: using var doc = JsonDocument.Parse(json); return doc.RootElement.Clone();

7. Potential empty-prompt edge case when options.Instructions is set but all messages have only non-text content

  • File: src/AI/Microsoft.Maui.Essentials.AI/Platform/Windows/PhiSilicaChatClient.cs
  • Lines: 78–82
  • Scenario: When options.Instructions is provided, NormalizeChatMessages returns it as system prompt and keeps all messages (including system role messages) in history. If the only message is a system message (now used as both Instructions context and still in history), the ConvertToPrompt will include a System: ... prefix which may confuse the model since it's already in the context.
  • Recommendation: When options.Instructions is set, consider filtering out the original system message from history if it duplicates the instructions, or document that Instructions takes precedence and system messages are preserved as-is.

8. Silent exception swallowing in tool enum extraction

  • File: samples/Microsoft.Maui.Essentials.AI.Sample/Services/PhiSilicaToolsAndSchemaClient.cs
  • Line: 206
  • Scenario: catch { } silently swallows all exceptions from JSON parsing of tool schemas. If a tool schema is malformed, the user gets no diagnostic feedback.
  • Recommendation: At minimum log a warning or Debug.Write so developers can troubleshoot schema issues.

Summary

Severity Count
🔴 CRITICAL 1
🟡 MODERATE 4
🟢 MINOR 3

The critical DI issue (#1) will cause a runtime crash for any Windows user of the sample app when ISemanticSearchService is resolved. The moderate issues are correctness/safety concerns in the shipped library code. The minor issues are in sample code and represent best-practice gaps.

Warning

⚠️ Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • learn.microsoft.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "learn.microsoft.com"

See Network Configuration for more information.

Generated by Expert Code Review (auto) for issue #178 · ● 24.1M

Comment thread samples/EssentialsAISample/MauiProgram.cs
Comment thread src/AI/Microsoft.Maui.Essentials.AI/Platform/Windows/PhiSilicaChatClient.cs Outdated
mattleibow added a commit that referenced this pull request Apr 29, 2026
- PhiSilicaChatClient: save CancellationTokenRegistration + try/finally
  with operation.Cancel()+registration.Dispose() for early streaming exit
- PhiSilicaChatClient: fix Dispose() to chain ContinueWith for in-flight
  _modelTask to avoid leaking LanguageModel COM object
- PhiSilicaToolsAndSchemaClient: catch (JsonException) instead of bare catch
- PhiSilicaToolsAndSchemaClient: use 'using var' for JsonDocument instances
- AppContentIndexerSearchService: add _indexersLock + lock in GetOrCreateIndexer,
  WaitUntilReadyAsync, and Dispose for thread safety
- MauiProgram: guard EmbeddingSearchService registration in #if !WINDOWS
  to prevent DI override of Windows AppContentIndexerSearchService

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
mattleibow added a commit that referenced this pull request May 2, 2026
- PhiSilicaChatClient: save CancellationTokenRegistration + try/finally
  with operation.Cancel()+registration.Dispose() for early streaming exit
- PhiSilicaChatClient: fix Dispose() to chain ContinueWith for in-flight
  _modelTask to avoid leaking LanguageModel COM object
- PhiSilicaToolsAndSchemaClient: catch (JsonException) instead of bare catch
- PhiSilicaToolsAndSchemaClient: use 'using var' for JsonDocument instances
- AppContentIndexerSearchService: add _indexersLock + lock in GetOrCreateIndexer,
  WaitUntilReadyAsync, and Dispose for thread safety
- MauiProgram: guard EmbeddingSearchService registration in #if !WINDOWS
  to prevent DI override of Windows AppContentIndexerSearchService

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mattleibow mattleibow force-pushed the mattleibow/ai-windows-phi-silica branch from de9cc86 to 06352f5 Compare May 2, 2026 01:54
Base automatically changed from mattleibow/ai-migration-plan to main May 2, 2026 11:35
mattleibow added a commit that referenced this pull request May 4, 2026
- PhiSilicaChatClient: save CancellationTokenRegistration + try/finally
  with operation.Cancel()+registration.Dispose() for early streaming exit
- PhiSilicaChatClient: fix Dispose() to chain ContinueWith for in-flight
  _modelTask to avoid leaking LanguageModel COM object
- PhiSilicaToolsAndSchemaClient: catch (JsonException) instead of bare catch
- PhiSilicaToolsAndSchemaClient: use 'using var' for JsonDocument instances
- AppContentIndexerSearchService: add _indexersLock + lock in GetOrCreateIndexer,
  WaitUntilReadyAsync, and Dispose for thread safety
- MauiProgram: guard EmbeddingSearchService registration in #if !WINDOWS
  to prevent DI override of Windows AppContentIndexerSearchService

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mattleibow mattleibow force-pushed the mattleibow/ai-windows-phi-silica branch from 06352f5 to 56bb204 Compare May 4, 2026 22:16
mattleibow and others added 6 commits May 12, 2026 11:36
- Add PhiSilicaChatClient, PhiSilicaExtensions, PhiSilicaModelFactory
- Add Windows test files: PhiSilicaChatClientTests, PhiSilicaExperimentTests
- Mark 3 base test methods virtual for Phi Silica overrides
- Add AppContentIndexerSearchService + PhiSilicaToolsAndSchemaClient to sample
- Register AddPhiSilicaServices in MauiProgram.cs (#elif WINDOWS)
- Update Windows Package.appxmanifest: systemAIModels capability, MaxVersionTested=10.0.26226.0
- Add Microsoft.WindowsAppSDK VersionOverride=2.0.0-experimental6 (AI projects only)
- Add AppxOSMaxVersionTestedReplaceManifestVersion=false to prevent SDK override
- Add SelfContained + WindowsAppSDKSelfContained for DeviceTests Windows build
- Update PublicAPI/net-windows/PublicAPI.Unshipped.txt with 7 new API entries
- Move TOOL-CALLING-README.md to docs/ai/
- Add src/AI/Directory.Build.props (chained import only)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- PhiSilicaChatClient: save CancellationTokenRegistration + try/finally
  with operation.Cancel()+registration.Dispose() for early streaming exit
- PhiSilicaChatClient: fix Dispose() to chain ContinueWith for in-flight
  _modelTask to avoid leaking LanguageModel COM object
- PhiSilicaToolsAndSchemaClient: catch (JsonException) instead of bare catch
- PhiSilicaToolsAndSchemaClient: use 'using var' for JsonDocument instances
- AppContentIndexerSearchService: add _indexersLock + lock in GetOrCreateIndexer,
  WaitUntilReadyAsync, and Dispose for thread safety
- MauiProgram: guard EmbeddingSearchService registration in #if !WINDOWS
  to prevent DI override of Windows AppContentIndexerSearchService

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add Microsoft.Windows.SDK.BuildTools.WinApp to sample and device tests
  to enable dotnet run for packaged MSIX apps via winapp CLI
- Remove WindowsPackageType=None from sample (was running unpackaged,
  blocking systemAIModels capability and GetReadyState)
- Add SelfContained/WindowsAppSDKSelfContained to sample for Windows
- Add MODE_XHARNESS DefineConstants when TestingMode=XHarness
- Add temporary arg-passing proof-of-concept in App.xaml.cs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…mple

Update namespace in Windows-specific service files and device tests
to match the new sample project name (PR #171 rename).

- AppContentIndexerSearchService.cs: Maui.Controls.Sample.Services -> EssentialsAISample.Services
- PhiSilicaToolsAndSchemaClient.cs: Maui.Controls.Sample.Services -> EssentialsAISample.Services
- PhiSilicaChatClientTests.cs: using -> EssentialsAISample.Services
- App.xaml.cs: Remove temporary arg-passing test code

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The experimental Microsoft.WindowsAppSDK 2.0.0-experimental6 package
and its transitive dependencies (Microsoft.WindowsAppSDK.Runtime,
Microsoft.Windows.AI.MachineLearning) are not available in the dnceng
NuGet feeds on macOS. Use the same IsOSPlatform('windows') conditional
pattern as the sample and test projects to exclude the Windows TFM
when building on non-Windows platforms.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Microsoft.WindowsAppSDK 2.0.0-experimental6 has a transitive dependency
on Microsoft.WindowsAppSDK.Search which is only available on nuget.org,
not in the dotnet-public AzDO upstream mirror.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jfversluis jfversluis force-pushed the mattleibow/ai-windows-phi-silica branch from e91177c to b4c0008 Compare May 12, 2026 09:40
…nerability

Upgrade Microsoft.Agents.AI from 1.0.0-rc2 to 1.5.0 (stable) and
Microsoft.Agents.AI.Hosting from 1.0.0-preview to 1.5.0-preview to
resolve NU1902: transitive OpenTelemetry.Api 1.13.1 has a known
moderate vulnerability (GHSA-g94r-2vxg-569j).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jfversluis jfversluis force-pushed the mattleibow/ai-windows-phi-silica branch from c9abd4c to 428e9dd Compare May 12, 2026 10:22
- Microsoft.Extensions.DependencyInjection: 10.0.5 -> 10.0.6
- Microsoft.Extensions.Logging.Abstractions: 10.0.5 -> 10.0.6
- Microsoft.Extensions.AI: 10.3.0 -> 10.5.2
- Microsoft.Extensions.AI.Abstractions: 10.3.0 -> 10.5.2

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants