diff --git a/.gitignore b/.gitignore
index 1b1f680..406a20e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,3 +31,4 @@ Cargo.lock
bin/
obj/
/src/cs/samples/ConsoleClient/test.http
+logs/
\ No newline at end of file
diff --git a/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj b/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj
index 113bebd..105dd0e 100644
--- a/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj
+++ b/sdk_v2/cs/src/Microsoft.AI.Foundry.Local.csproj
@@ -98,9 +98,8 @@
$(FoundryLocalCoreVersion)
- 0.9.0.2-dev-20260226T191541-2b332047
- 0.9.0.4-dev-20260226T191638-2b332047
-
+ 0.9.0-dev-20260227T230631-2a3af92
+ 0.9.0-dev-20260227T222239-2a3af92
True
@@ -109,6 +108,11 @@
True
+
+
+ $(NoWarn);NU1604
+
diff --git a/sdk_v2/cs/src/OpenAI/AudioClient.cs b/sdk_v2/cs/src/OpenAI/AudioClient.cs
index 245fbdf..5475185 100644
--- a/sdk_v2/cs/src/OpenAI/AudioClient.cs
+++ b/sdk_v2/cs/src/OpenAI/AudioClient.cs
@@ -138,7 +138,7 @@ private async IAsyncEnumerable TranscribeAudio
{
var failed = false;
- await _coreInterop.ExecuteCommandWithCallbackAsync(
+ var res = await _coreInterop.ExecuteCommandWithCallbackAsync(
"audio_transcribe",
request,
async (callbackData) =>
@@ -163,6 +163,16 @@ await _coreInterop.ExecuteCommandWithCallbackAsync(
ct
).ConfigureAwait(false);
+ // If the native layer returned an error (e.g. missing audio file, invalid model)
+ // without invoking any callbacks, propagate it so the caller sees an exception
+ // instead of an empty stream.
+ if (res.Error != null)
+ {
+ channel.Writer.TryComplete(
+ new FoundryLocalException($"Error from audio_transcribe command: {res.Error}", _logger));
+ return;
+ }
+
// use TryComplete as an exception in the callback may have already closed the channel
_ = channel.Writer.TryComplete();
}
diff --git a/sdk_v2/cs/src/OpenAI/ChatClient.cs b/sdk_v2/cs/src/OpenAI/ChatClient.cs
index e237ee3..b9f889f 100644
--- a/sdk_v2/cs/src/OpenAI/ChatClient.cs
+++ b/sdk_v2/cs/src/OpenAI/ChatClient.cs
@@ -171,7 +171,7 @@ private async IAsyncEnumerable ChatStreamingImplAs
{
var failed = false;
- await _coreInterop.ExecuteCommandWithCallbackAsync(
+ var response = await _coreInterop.ExecuteCommandWithCallbackAsync(
"chat_completions",
request,
async (callbackData) =>
@@ -196,6 +196,17 @@ await _coreInterop.ExecuteCommandWithCallbackAsync(
ct
).ConfigureAwait(false);
+ // If the native layer returned an error (e.g. missing model, invalid input)
+ // without invoking any callbacks, propagate it so the caller sees an exception
+ // instead of an empty stream.
+ if (!failed && response.Error != null)
+ {
+ channel.Writer.TryComplete(
+ new FoundryLocalException($"Error from chat_completions command: {response.Error}", _logger));
+ failed = true;
+ return;
+ }
+
// use TryComplete as an exception in the callback may have already closed the channel
_ = channel.Writer.TryComplete();
}
diff --git a/sdk_v2/cs/test/FoundryLocal.Tests/AudioClientTests.cs b/sdk_v2/cs/test/FoundryLocal.Tests/AudioClientTests.cs
index c887563..ec4ab4c 100644
--- a/sdk_v2/cs/test/FoundryLocal.Tests/AudioClientTests.cs
+++ b/sdk_v2/cs/test/FoundryLocal.Tests/AudioClientTests.cs
@@ -67,6 +67,33 @@ public async Task AudioTranscription_NoStreaming_Succeeds_WithTemperature()
Console.WriteLine($"Response: {content}");
}
+ [Test]
+ public async Task AudioTranscription_NoStreaming_InValidFile()
+ {
+ var audioClient = await model!.GetAudioClientAsync();
+ await Assert.That(audioClient).IsNotNull();
+
+ audioClient.Settings.Language = "en";
+
+ var audioFilePath = Path.Combine(AppContext.BaseDirectory, "testdata/non_exist_Recording.mp3");
+
+ FoundryLocalException? caught = null;
+ try
+ {
+ await audioClient.TranscribeAudioAsync(audioFilePath).ConfigureAwait(false);
+ }
+ catch (FoundryLocalException ex)
+ {
+ caught = ex;
+ }
+
+ // Assert: a FoundryLocalException must have been thrown
+ await Assert.That(caught).IsNotNull();
+ Console.WriteLine($"Caught exception: {caught}");
+ await Assert.That(caught!.Message).Contains("Audio file not found");
+
+ }
+
[Test]
public async Task AudioTranscription_Streaming_Succeeds()
{
@@ -123,4 +150,33 @@ public async Task AudioTranscription_Streaming_Succeeds_WithTemperature()
}
+
+ [Test]
+ public async Task AudioTranscription_Streaming_InvalidFiles()
+ {
+ var audioClient = await model!.GetAudioClientAsync();
+ await Assert.That(audioClient).IsNotNull();
+
+ audioClient.Settings.Language = "en";
+
+ var audioFilePath = Path.Combine(AppContext.BaseDirectory, "testdata/Record.mp3");
+
+ FoundryLocalException? caught = null;
+ try
+ {
+ await foreach (var _ in audioClient.TranscribeAudioStreamingAsync(audioFilePath, CancellationToken.None).ConfigureAwait(false))
+ {
+ }
+ }
+ catch (FoundryLocalException ex)
+ {
+ caught = ex;
+ }
+
+ // Assert: a FoundryLocalException must have been thrown
+ await Assert.That(caught).IsNotNull();
+ Console.WriteLine($"Caught exception: {caught}");
+ await Assert.That(caught!.Message).Contains("Audio file not found");
+
+ }
}
diff --git a/sdk_v2/js/logs/foundry.core20260226.log b/sdk_v2/js/logs/foundry.core20260226.log
deleted file mode 100644
index 4f44c4f..0000000
--- a/sdk_v2/js/logs/foundry.core20260226.log
+++ /dev/null
@@ -1,16 +0,0 @@
-2026-02-26 15:53:10.767 -08:00 [WRN] {"Params":{"OpenAICreateRequest":"{\"Model\":\"openai-whisper-tiny-generic-cpu:2\",\"FileName\":\"C:\\\\foundry-local\\\\Foundry-Local\\\\sdk_v2\\\\testdata\\\\Recording.mp3\",\"Language\":\"en\",\"Temperature\":0,\"metadata\":{\"language\":\"en\",\"temperature\":\"0\"}}"}
-2026-02-26 15:53:12.159 -08:00 [WRN] {"Params":{"OpenAICreateRequest":"{\"Model\":\"openai-whisper-tiny-generic-cpu:2\",\"FileName\":\"C:\\\\foundry-local\\\\Foundry-Local\\\\sdk_v2\\\\testdata\\\\Recording.mp3\",\"Language\":\"en\",\"Temperature\":0,\"metadata\":{\"language\":\"en\",\"temperature\":\"0\"}}"}
-2026-02-26 16:00:39.395 -08:00 [WRN] {"Params":{"OpenAICreateRequest":"{\"Model\":\"openai-whisper-tiny-generic-cpu:2\",\"FileName\":\"C:\\\\foundry-local\\\\Foundry-Local\\\\sdk_v2\\\\testdata\\\\Recording.mp3\",\"Language\":\"en\",\"Temperature\":0,\"metadata\":{\"language\":\"en\",\"temperature\":\"0\"}}"}
-2026-02-26 16:00:40.683 -08:00 [WRN] {"Params":{"OpenAICreateRequest":"{\"Model\":\"openai-whisper-tiny-generic-cpu:2\",\"FileName\":\"C:\\\\foundry-local\\\\Foundry-Local\\\\sdk_v2\\\\testdata\\\\Recording.mp3\",\"Language\":\"en\",\"Temperature\":0,\"metadata\":{\"language\":\"en\",\"temperature\":\"0\"}}"}
-2026-02-26 16:03:53.744 -08:00 [WRN] {"Params":{"OpenAICreateRequest":"{\"Model\":\"openai-whisper-tiny-generic-cpu:2\",\"FileName\":\"C:\\\\foundry-local\\\\Foundry-Local\\\\sdk_v2\\\\testdata\\\\Recording.mp3\",\"Language\":\"en\",\"Temperature\":0,\"metadata\":{\"language\":\"en\",\"temperature\":\"0\"}}"}
-2026-02-26 16:03:55.150 -08:00 [WRN] {"Params":{"OpenAICreateRequest":"{\"Model\":\"openai-whisper-tiny-generic-cpu:2\",\"FileName\":\"C:\\\\foundry-local\\\\Foundry-Local\\\\sdk_v2\\\\testdata\\\\Recording.mp3\",\"Language\":\"en\",\"Temperature\":0,\"metadata\":{\"language\":\"en\",\"temperature\":\"0\"}}"}
-2026-02-26 16:05:42.755 -08:00 [WRN] {"Params":{"OpenAICreateRequest":"{\"Model\":\"openai-whisper-tiny-generic-cpu:2\",\"FileName\":\"C:\\\\foundry-local\\\\Foundry-Local\\\\sdk_v2\\\\testdata\\\\Recording.mp3\",\"Language\":\"en\",\"Temperature\":0,\"metadata\":{\"language\":\"en\",\"temperature\":\"0\"}}"}
-2026-02-26 16:05:44.077 -08:00 [WRN] {"Params":{"OpenAICreateRequest":"{\"Model\":\"openai-whisper-tiny-generic-cpu:2\",\"FileName\":\"C:\\\\foundry-local\\\\Foundry-Local\\\\sdk_v2\\\\testdata\\\\Recording.mp3\",\"Language\":\"en\",\"Temperature\":0,\"metadata\":{\"language\":\"en\",\"temperature\":\"0\"}}"}
-2026-02-26 16:08:42.903 -08:00 [WRN] {"Params":{"OpenAICreateRequest":"{\"Model\":\"openai-whisper-tiny-generic-cpu:2\",\"FileName\":\"C:\\\\foundry-local\\\\Foundry-Local\\\\sdk_v2\\\\testdata\\\\Recording.mp3\",\"Language\":\"en\",\"Temperature\":0,\"metadata\":{\"language\":\"en\",\"temperature\":\"0\"}}"}
-2026-02-26 16:08:44.319 -08:00 [WRN] {"Params":{"OpenAICreateRequest":"{\"Model\":\"openai-whisper-tiny-generic-cpu:2\",\"FileName\":\"C:\\\\foundry-local\\\\Foundry-Local\\\\sdk_v2\\\\testdata\\\\Recording.mp3\",\"Language\":\"en\",\"Temperature\":0,\"metadata\":{\"language\":\"en\",\"temperature\":\"0\"}}"}
-2026-02-26 16:11:14.418 -08:00 [WRN] {"Params":{"OpenAICreateRequest":"{\"Model\":\"openai-whisper-tiny-generic-cpu:2\",\"FileName\":\"C:\\\\foundry-local\\\\Foundry-Local\\\\sdk_v2\\\\testdata\\\\Recording.mp3\",\"Language\":\"en\",\"Temperature\":0,\"metadata\":{\"language\":\"en\",\"temperature\":\"0\"}}"}
-2026-02-26 16:11:15.709 -08:00 [WRN] {"Params":{"OpenAICreateRequest":"{\"Model\":\"openai-whisper-tiny-generic-cpu:2\",\"FileName\":\"C:\\\\foundry-local\\\\Foundry-Local\\\\sdk_v2\\\\testdata\\\\Recording.mp3\",\"Language\":\"en\",\"Temperature\":0,\"metadata\":{\"language\":\"en\",\"temperature\":\"0\"}}"}
-2026-02-26 16:12:34.632 -08:00 [WRN] {"Params":{"OpenAICreateRequest":"{\"Model\":\"openai-whisper-tiny-generic-cpu:2\",\"FileName\":\"C:\\\\foundry-local\\\\Foundry-Local\\\\sdk_v2\\\\testdata\\\\Recording.mp3\",\"Language\":\"en\",\"Temperature\":0,\"metadata\":{\"language\":\"en\",\"temperature\":\"0\"}}"}
-2026-02-26 16:12:39.679 -08:00 [WRN] {"Params":{"OpenAICreateRequest":"{\"Model\":\"openai-whisper-tiny-generic-cpu:2\",\"FileName\":\"C:\\\\foundry-local\\\\Foundry-Local\\\\sdk_v2\\\\testdata\\\\Recording.mp3\",\"Language\":\"en\",\"Temperature\":0,\"metadata\":{\"language\":\"en\",\"temperature\":\"0\"}}"}
-2026-02-26 16:15:46.477 -08:00 [WRN] {"Params":{"OpenAICreateRequest":"{\"Model\":\"openai-whisper-tiny-generic-cpu:2\",\"FileName\":\"C:\\\\foundry-local\\\\Foundry-Local\\\\sdk_v2\\\\testdata\\\\Recording.mp3\",\"Language\":\"en\",\"Temperature\":0,\"metadata\":{\"language\":\"en\",\"temperature\":\"0\"}}"}
-2026-02-26 16:15:47.873 -08:00 [WRN] {"Params":{"OpenAICreateRequest":"{\"Model\":\"openai-whisper-tiny-generic-cpu:2\",\"FileName\":\"C:\\\\foundry-local\\\\Foundry-Local\\\\sdk_v2\\\\testdata\\\\Recording.mp3\",\"Language\":\"en\",\"Temperature\":0,\"metadata\":{\"language\":\"en\",\"temperature\":\"0\"}}"}