Skip to content

Commit 0077d80

Browse files
committed
Merge branch 'main' of https://github.com/microsoft/Foundry-Local into copilot/update-language-bindings-documentation
2 parents 55c65d9 + 2035462 commit 0077d80

File tree

57 files changed

+2590
-161
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+2590
-161
lines changed

samples/cs/audio-transcription-example/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
// EP packages include dependencies and may be large.
2121
// Download is only required again if a new version of the EP is released.
2222
// For cross platform builds there is no dynamic EP download and this will return immediately.
23-
await Utils.RunWithSpinner("Registering execution providers", mgr.EnsureEpsDownloadedAsync());
23+
await Utils.RunWithSpinner("Registering execution providers", mgr.DownloadAndRegisterEpsAsync());
2424
// </init>
2525

2626

@@ -56,6 +56,7 @@ await model.DownloadAsync(progress =>
5656
// <transcription>
5757
// Get an audio client
5858
var audioClient = await model.GetAudioClientAsync();
59+
audioClient.Settings.Language = "en";
5960

6061
// Get a transcription with streaming outputs
6162
var audioFile = args.Length > 0 ? args[0] : Path.Combine(AppContext.BaseDirectory, "Recording.mp3");

samples/cs/foundry-local-web-server/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
// EP packages include dependencies and may be large.
2727
// Download is only required again if a new version of the EP is released.
2828
// For cross platform builds there is no dynamic EP download and this will return immediately.
29-
await Utils.RunWithSpinner("Registering execution providers", mgr.EnsureEpsDownloadedAsync());
29+
await Utils.RunWithSpinner("Registering execution providers", mgr.DownloadAndRegisterEpsAsync());
3030
// </init>
3131

3232

samples/cs/live-audio-transcription-example/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
await FoundryLocalManager.CreateAsync(config, Utils.GetAppLogger());
2121
var mgr = FoundryLocalManager.Instance;
2222

23-
await Utils.RunWithSpinner("Registering execution providers", mgr.EnsureEpsDownloadedAsync());
23+
await mgr.DownloadAndRegisterEpsAsync();
2424

2525
var catalog = await mgr.GetCatalogAsync();
2626

samples/cs/model-management-example/Program.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,8 @@
1616
var mgr = FoundryLocalManager.Instance;
1717

1818

19-
// Ensure that any Execution Provider (EP) downloads run and are completed.
20-
// EP packages include dependencies and may be large.
21-
// Download is only required again if a new version of the EP is released.
22-
// For cross platform builds there is no dynamic EP download and this will return immediately.
23-
await Utils.RunWithSpinner("Registering execution providers", mgr.EnsureEpsDownloadedAsync());
19+
// Download and register all execution providers.
20+
await Utils.RunWithSpinner("Registering execution providers", mgr.DownloadAndRegisterEpsAsync());
2421

2522

2623
// Model catalog operations

samples/cs/native-chat-completions/Program.cs

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,43 @@
1919
var mgr = FoundryLocalManager.Instance;
2020

2121

22-
// Ensure that any Execution Provider (EP) downloads run and are completed.
22+
// Discover available execution providers and their registration status.
23+
var eps = mgr.DiscoverEps();
24+
Console.WriteLine("Available execution providers:");
25+
foreach (var ep in eps)
26+
{
27+
Console.WriteLine($" {ep.Name} (registered: {ep.IsRegistered})");
28+
}
29+
30+
// Download and register all execution providers with per-EP progress.
2331
// EP packages include dependencies and may be large.
2432
// Download is only required again if a new version of the EP is released.
2533
// For cross platform builds there is no dynamic EP download and this will return immediately.
26-
await Utils.RunWithSpinner("Registering execution providers", mgr.EnsureEpsDownloadedAsync());
34+
if (eps.Length > 0)
35+
{
36+
int maxNameLen = eps.Max(e => e.Name.Length);
37+
string currentEp = "";
38+
await mgr.DownloadAndRegisterEpsAsync((epName, percent) =>
39+
{
40+
if (epName != currentEp)
41+
{
42+
if (currentEp != "")
43+
{
44+
Console.WriteLine();
45+
}
46+
currentEp = epName;
47+
}
48+
Console.Write($"\r {epName.PadRight(maxNameLen)} {percent,6:F1}%");
49+
if (percent >= 100)
50+
{
51+
Console.WriteLine();
52+
}
53+
});
54+
}
55+
else
56+
{
57+
Console.WriteLine("No execution providers to download.");
58+
}
2759
// </init>
2860

2961

samples/cs/tool-calling-foundry-local-sdk/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
// EP packages include dependencies and may be large.
2727
// Download is only required again if a new version of the EP is released.
2828
// For cross platform builds there is no dynamic EP download and this will return immediately.
29-
await Utils.RunWithSpinner("Registering execution providers", mgr.EnsureEpsDownloadedAsync());
29+
await Utils.RunWithSpinner("Registering execution providers", mgr.DownloadAndRegisterEpsAsync());
3030
// </init>
3131

3232

samples/cs/tool-calling-foundry-local-web-server/Program.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,8 @@
2121
var mgr = FoundryLocalManager.Instance;
2222

2323

24-
// Ensure that any Execution Provider (EP) downloads run and are completed.
25-
// EP packages include dependencies and may be large.
26-
// Download is only required again if a new version of the EP is released.
27-
// For cross platform builds there is no dynamic EP download and this will return immediately.
28-
await Utils.RunWithSpinner("Registering execution providers", mgr.EnsureEpsDownloadedAsync());
24+
// Download and register all execution providers.
25+
await Utils.RunWithSpinner("Registering execution providers", mgr.DownloadAndRegisterEpsAsync());
2926

3027

3128
// Get the model catalog
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Live Audio Transcription Example
2+
3+
Real-time microphone-to-text transcription using the Foundry Local JS SDK with Nemotron ASR.
4+
5+
## Prerequisites
6+
7+
- [Foundry Local](https://github.com/microsoft/Foundry-Local) installed
8+
- Node.js 18+
9+
- A microphone (optional — falls back to synthetic audio)
10+
11+
## Setup
12+
13+
```bash
14+
npm install foundry-local-sdk naudiodon2
15+
```
16+
17+
> **Note:** `naudiodon2` is optional — provides cross-platform microphone capture. Without it, the example falls back to synthetic audio for testing.
18+
19+
## Run
20+
21+
```bash
22+
node app.js
23+
```
24+
25+
Speak into your microphone. Transcription appears in real-time. Press `Ctrl+C` to stop.
26+
27+
## How it works
28+
29+
1. Initializes the Foundry Local SDK and loads the Nemotron ASR model
30+
2. Creates a `LiveAudioTranscriptionSession` with 16kHz/16-bit/mono PCM settings
31+
3. Captures microphone audio via `naudiodon2` (or generates synthetic audio as fallback)
32+
4. Pushes PCM chunks to the SDK via `session.append()`
33+
5. Reads transcription results via `for await (const result of session.getTranscriptionStream())`
34+
6. Access text via `result.content[0].text` (OpenAI Realtime ConversationItem pattern)
35+
36+
## API
37+
38+
```javascript
39+
const audioClient = model.createAudioClient();
40+
const session = audioClient.createLiveTranscriptionSession();
41+
session.settings.sampleRate = 16000;
42+
session.settings.channels = 1;
43+
session.settings.language = 'en';
44+
45+
await session.start();
46+
47+
// Push audio
48+
await session.append(pcmBytes);
49+
50+
// Read results
51+
for await (const result of session.getTranscriptionStream()) {
52+
console.log(result.content[0].text); // transcribed text
53+
console.log(result.content[0].transcript); // alias (OpenAI compat)
54+
console.log(result.is_final); // true for final results
55+
}
56+
57+
await session.stop();
58+
```
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
// Live Audio Transcription Example — Foundry Local JS SDK
2+
//
3+
// Demonstrates real-time microphone-to-text using the JS SDK.
4+
// Requires: npm install foundry-local-sdk naudiodon2
5+
//
6+
// Usage: node app.js
7+
8+
import { FoundryLocalManager } from 'foundry-local-sdk';
9+
10+
console.log('╔══════════════════════════════════════════════════════════╗');
11+
console.log('║ Foundry Local — Live Audio Transcription (JS SDK) ║');
12+
console.log('╚══════════════════════════════════════════════════════════╝');
13+
console.log();
14+
15+
// Initialize the Foundry Local SDK
16+
console.log('Initializing Foundry Local SDK...');
17+
const manager = FoundryLocalManager.create({
18+
appName: 'foundry_local_live_audio',
19+
logLevel: 'info'
20+
});
21+
console.log('✓ SDK initialized');
22+
23+
// Get and load the nemotron model
24+
const modelAlias = 'nemotron';
25+
let model = await manager.catalog.getModel(modelAlias);
26+
if (!model) {
27+
console.error(`ERROR: Model "${modelAlias}" not found in catalog.`);
28+
process.exit(1);
29+
}
30+
31+
console.log(`Found model: ${model.id}`);
32+
console.log('Downloading model (if needed)...');
33+
await model.download((progress) => {
34+
process.stdout.write(`\rDownloading... ${progress.toFixed(2)}%`);
35+
});
36+
console.log('\n✓ Model downloaded');
37+
38+
console.log('Loading model...');
39+
await model.load();
40+
console.log('✓ Model loaded');
41+
42+
// Create live transcription session
43+
const audioClient = model.createAudioClient();
44+
const session = audioClient.createLiveTranscriptionSession();
45+
session.settings.sampleRate = 16000; // Default is 16000; shown here for clarity
46+
session.settings.channels = 1;
47+
session.settings.bitsPerSample = 16;
48+
session.settings.language = 'en';
49+
50+
console.log('Starting streaming session...');
51+
await session.start();
52+
console.log('✓ Session started');
53+
54+
// Read transcription results in background
55+
const readPromise = (async () => {
56+
try {
57+
for await (const result of session.getTranscriptionStream()) {
58+
const text = result.content?.[0]?.text;
59+
if (result.is_final) {
60+
console.log();
61+
console.log(` [FINAL] ${text}`);
62+
} else if (text) {
63+
process.stdout.write(text);
64+
}
65+
}
66+
} catch (err) {
67+
if (err.name !== 'AbortError') {
68+
console.error('Stream error:', err.message);
69+
}
70+
}
71+
})();
72+
73+
// --- Microphone capture ---
74+
// This example uses naudiodon2 for cross-platform audio capture.
75+
// Install with: npm install naudiodon2
76+
//
77+
// If you prefer a different audio library, just push PCM bytes
78+
// (16-bit signed LE, mono, 16kHz) via session.append().
79+
80+
let audioInput;
81+
try {
82+
const { default: portAudio } = await import('naudiodon2');
83+
84+
audioInput = portAudio.AudioIO({
85+
inOptions: {
86+
channelCount: session.settings.channels,
87+
sampleFormat: session.settings.bitsPerSample === 16
88+
? portAudio.SampleFormat16Bit
89+
: portAudio.SampleFormat32Bit,
90+
sampleRate: session.settings.sampleRate,
91+
framesPerBuffer: 1600, // 100ms chunks
92+
maxQueue: 15 // buffer during event-loop blocks from sync FFI calls
93+
}
94+
});
95+
96+
let appendPending = false;
97+
audioInput.on('data', (buffer) => {
98+
if (appendPending) return; // drop frame while backpressured
99+
const pcm = new Uint8Array(buffer);
100+
appendPending = true;
101+
session.append(pcm).then(() => {
102+
appendPending = false;
103+
}).catch((err) => {
104+
appendPending = false;
105+
console.error('append error:', err.message);
106+
});
107+
});
108+
109+
console.log();
110+
console.log('════════════════════════════════════════════════════════════');
111+
console.log(' LIVE TRANSCRIPTION ACTIVE');
112+
console.log(' Speak into your microphone.');
113+
console.log(' Press Ctrl+C to stop.');
114+
console.log('════════════════════════════════════════════════════════════');
115+
console.log();
116+
117+
audioInput.start();
118+
} catch (err) {
119+
console.warn('⚠ Could not initialize microphone (naudiodon2 may not be installed).');
120+
console.warn(' Install with: npm install naudiodon2');
121+
console.warn(' Falling back to synthetic audio test...');
122+
console.warn();
123+
124+
// Fallback: push 2 seconds of synthetic PCM (440Hz sine wave)
125+
const sampleRate = session.settings.sampleRate;
126+
const duration = 2;
127+
const totalSamples = sampleRate * duration;
128+
const pcmBytes = new Uint8Array(totalSamples * 2);
129+
for (let i = 0; i < totalSamples; i++) {
130+
const t = i / sampleRate;
131+
const sample = Math.round(32767 * 0.5 * Math.sin(2 * Math.PI * 440 * t));
132+
pcmBytes[i * 2] = sample & 0xFF;
133+
pcmBytes[i * 2 + 1] = (sample >> 8) & 0xFF;
134+
}
135+
136+
// Push in 100ms chunks
137+
const chunkSize = (sampleRate / 10) * 2;
138+
for (let offset = 0; offset < pcmBytes.length; offset += chunkSize) {
139+
const len = Math.min(chunkSize, pcmBytes.length - offset);
140+
await session.append(pcmBytes.slice(offset, offset + len));
141+
}
142+
143+
console.log('✓ Synthetic audio pushed');
144+
}
145+
146+
// Handle graceful shutdown
147+
process.on('SIGINT', async () => {
148+
console.log('\n\nStopping...');
149+
if (audioInput) {
150+
audioInput.quit();
151+
}
152+
await session.stop();
153+
await readPromise;
154+
await model.unload();
155+
console.log('✓ Done');
156+
process.exit(0);
157+
});

samples/js/native-chat-completions/app.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,35 @@ const manager = FoundryLocalManager.create({
1414
// </init>
1515
console.log('✓ SDK initialized successfully');
1616

17+
// Discover available execution providers and their registration status.
18+
const eps = manager.discoverEps();
19+
console.log('\nAvailable execution providers:');
20+
for (const ep of eps) {
21+
console.log(` ${ep.name} (registered: ${ep.isRegistered})`);
22+
}
23+
24+
// Download and register all execution providers with per-EP progress.
25+
// EP packages include dependencies and may be large.
26+
// Download is only required again if a new version of the EP is released.
27+
if (eps.length > 0) {
28+
const maxNameLen = Math.max(...eps.map(e => e.name.length));
29+
let currentEp = '';
30+
await manager.downloadAndRegisterEps((epName, percent) => {
31+
if (epName !== currentEp) {
32+
if (currentEp !== '') {
33+
process.stdout.write('\n');
34+
}
35+
currentEp = epName;
36+
}
37+
process.stdout.write(`\r ${epName.padEnd(maxNameLen)} ${percent.toFixed(1).padStart(5)}%`);
38+
if (percent >= 100) {
39+
process.stdout.write('\n');
40+
}
41+
});
42+
} else {
43+
console.log('No execution providers to download.');
44+
}
45+
1746
// <model_setup>
1847
// Get the model object
1948
const modelAlias = 'qwen2.5-0.5b'; // Using an available model from the list above

0 commit comments

Comments
 (0)