Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions samples/cpp/live-audio-transcription/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ g++ -std=c++20 main.cpp -lfoundry_local -o live-audio-transcription-example
# Synthetic 440Hz sine wave (no microphone needed)
./live-audio-transcription-example --synth
```

Press `Ctrl+C` to request a graceful stop. The sample passes that signal to
execution-provider and model downloads so long-running downloads can be
cancelled before transcription starts.
11 changes: 7 additions & 4 deletions samples/cpp/live-audio-transcription/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ int main(int argc, char* argv[]) {

foundry_local::Manager::Create(config);
auto& manager = foundry_local::Manager::Instance();
manager.EnsureEpsDownloaded();
auto isCancellationRequested = [] { return !g_running.load(); };
manager.EnsureEpsDownloaded(nullptr, isCancellationRequested);

auto& catalog = manager.GetCatalog();
auto* model = catalog.GetModel("nemotron-speech-streaming-en-0.6b");
Expand All @@ -131,9 +132,11 @@ int main(int argc, char* argv[]) {
}

std::cout << "Downloading model (if needed)..." << std::endl;
model->Download([](float pct) {
std::cout << "\rDownloading: " << pct << "% " << std::flush;
});
model->Download(
[](float pct) {
std::cout << "\rDownloading: " << pct << "% " << std::flush;
},
isCancellationRequested);
std::cout << std::endl;
std::cout << "Loading model..." << std::endl;
model->Load();
Expand Down
9 changes: 8 additions & 1 deletion sdk/cpp/include/foundry_local_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <string>
#include <vector>
#include <memory>
#include <functional>

#include <gsl/pointers>
#include <gsl/span>
Expand All @@ -20,6 +21,8 @@ namespace foundry_local::Internal {

namespace foundry_local {

using ExecutionProviderDownloadProgressCallback = std::function<void(std::string epName, double percentage)>;

class Manager final {
public:
Manager(const Manager&) = delete;
Expand Down Expand Up @@ -61,7 +64,11 @@ namespace foundry_local {

/// Ensure execution providers are downloaded and registered.
/// Once downloaded, EPs are not re-downloaded unless a new version is available.
void EnsureEpsDownloaded() const;
/// @param onProgress Optional callback receiving execution provider name and percentage progress.
/// @param isCancellationRequested Optional callback checked on each progress update.
/// Return true to cancel the in-progress download.
void EnsureEpsDownloaded(ExecutionProviderDownloadProgressCallback onProgress = nullptr,
CancellationCallback isCancellationRequested = nullptr) const;

private:
explicit Manager(Configuration configuration, ILogger* logger);
Expand Down
18 changes: 14 additions & 4 deletions sdk/cpp/include/model.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <memory>
#include <functional>
#include <filesystem>
#include <utility>

#include <gsl/pointers>
#include <gsl/span>
Expand All @@ -33,6 +34,7 @@ namespace foundry_local {
#endif

using DownloadProgressCallback = std::function<bool(float percentage)>;
using CancellationCallback = std::function<bool()>;

class IModel {
public:
Expand All @@ -43,7 +45,13 @@ namespace foundry_local {
virtual bool IsLoaded() const = 0;
virtual bool IsCached() const = 0;
virtual const std::filesystem::path& GetPath() const = 0;
virtual void Download(DownloadProgressCallback onProgress = nullptr) = 0;

/// Download the model to the local cache.
/// @param onProgress Optional callback receiving percentage progress. Return true to continue.
/// @param isCancellationRequested Optional callback checked on each progress update.
/// Return true to cancel the in-progress download.
virtual void Download(DownloadProgressCallback onProgress = nullptr,
CancellationCallback isCancellationRequested = nullptr) = 0;
virtual void Load() = 0;
virtual void Unload() = 0;
virtual void RemoveFromCache() = 0;
Expand Down Expand Up @@ -123,7 +131,8 @@ namespace foundry_local {

const ModelInfo& GetInfo() const;
const std::filesystem::path& GetPath() const override;
void Download(DownloadProgressCallback onProgress = nullptr) override;
void Download(DownloadProgressCallback onProgress = nullptr,
CancellationCallback isCancellationRequested = nullptr) override;
void Load() override;

bool IsLoaded() const override;
Expand Down Expand Up @@ -158,8 +167,9 @@ namespace foundry_local {
bool IsLoaded() const override { return SelectedVariant().IsLoaded(); }
bool IsCached() const override { return SelectedVariant().IsCached(); }
const std::filesystem::path& GetPath() const override { return SelectedVariant().GetPath(); }
void Download(DownloadProgressCallback onProgress = nullptr) override {
SelectedVariant().Download(std::move(onProgress));
void Download(DownloadProgressCallback onProgress = nullptr,
CancellationCallback isCancellationRequested = nullptr) override {
SelectedVariant().Download(std::move(onProgress), std::move(isCancellationRequested));
}
void Load() override { SelectedVariant().Load(); }
void Unload() override { SelectedVariant().Unload(); }
Expand Down
25 changes: 22 additions & 3 deletions sdk/cpp/sample/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#include "foundry_local.h"

#include <atomic>
#include <csignal>
#include <iostream>
#include <string>
#include <vector>
Expand All @@ -13,6 +15,18 @@

using namespace foundry_local;

namespace {
std::atomic<bool> g_cancelRequested{false};

void SignalHandler(int /*signum*/) {
g_cancelRequested.store(true);
}

bool IsCancellationRequested() {
return g_cancelRequested.load();
}
} // namespace

// ---------------------------------------------------------------------------
// Logger
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -117,7 +131,8 @@ void ChatNonStreaming(Manager& manager, const std::string& alias) {
PreferCpuVariant(*concreteModel);
}

model->Download([](float pct) { std::cout << "\rDownloading: " << pct << "% " << std::flush; return true; });
model->Download([](float pct) { std::cout << "\rDownloading: " << pct << "% " << std::flush; return true; },
IsCancellationRequested);
std::cout << "\n";

model->Load();
Expand Down Expand Up @@ -210,7 +225,8 @@ void TranscribeAudio(Manager& manager, const std::string& alias, const std::stri
PreferCpuVariant(*concreteModel);
}

model->Download([](float pct) { std::cout << "\rDownloading: " << pct << "% " << std::flush; return true; });
model->Download([](float pct) { std::cout << "\rDownloading: " << pct << "% " << std::flush; return true; },
IsCancellationRequested);
std::cout << "\n";

model->Load();
Expand Down Expand Up @@ -262,7 +278,8 @@ void ChatWithToolCalling(Manager& manager, const std::string& alias) {
PreferCpuVariant(*concreteModel);
}

model->Download([](float pct) { std::cout << "\rDownloading: " << pct << "% " << std::flush; return true; });
model->Download([](float pct) { std::cout << "\rDownloading: " << pct << "% " << std::flush; return true; },
IsCancellationRequested);
std::cout << "\n";

model->Load();
Expand Down Expand Up @@ -375,6 +392,8 @@ int main(int argc, char* argv[]) {
const std::string audioPath = (argc > 3) ? argv[3] : "";

try {
std::signal(SIGINT, SignalHandler);

StdLogger logger;
Manager::Create({"SampleApp"}, &logger);
auto& manager = Manager::Instance();
Expand Down
2 changes: 1 addition & 1 deletion sdk/cpp/src/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ namespace foundry_local {
std::unique_ptr<ResponseBuffer, decltype(safeDeleter)> responseGuard(&response, safeDeleter);

if (callback != nullptr) {
execCbCmd_(&request, &response, reinterpret_cast<void*>(callback), data);
execCbCmd_(&request, &response, callback, data);
}
else {
execCmd_(&request, &response);
Expand Down
103 changes: 91 additions & 12 deletions sdk/cpp/src/core_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@

#pragma once

#include <charconv>
#include <string>
#include <string_view>
#include <vector>
#include <functional>
#include <exception>
#include <system_error>
#include <unordered_map>
#include <utility>

#include <nlohmann/json.hpp>

Expand Down Expand Up @@ -47,38 +50,82 @@ namespace foundry_local::detail {
return core->call(command, logger, &payload, callback, userData);
}

inline bool TryParseFloatToken(std::string_view token, float& value) {
if (token.empty()) {
return false;
}

const auto* begin = token.data();
const auto* end = begin + token.size();
const auto result = std::from_chars(begin, end, value);
return result.ec == std::errc{} && result.ptr == end;
}

inline bool TryParseDoubleToken(std::string_view token, double& value) {
if (token.empty()) {
return false;
}

const auto* begin = token.data();
const auto* end = begin + token.size();
const auto result = std::from_chars(begin, end, value);
return result.ec == std::errc{} && result.ptr == end;
}

// Serialize + call with a streaming chunk handler.
// Wraps the caller-supplied onChunk with the native callback boilerplate
// (null/length checks, exception capture, rethrow after the call).
// (null/length checks, exception capture, cancellation, rethrow after the call).
// The errorContext string is used to prefix any core-layer error message.
inline CoreResponse CallWithStreamingCallback(Internal::IFoundryLocalCore* core, std::string_view command,
const std::string& payload, ILogger& logger,
const std::function<void(const std::string&)>& onChunk,
std::string_view errorContext) {
const std::string* payload, ILogger& logger,
const std::function<bool(const std::string&)>& onChunk,
std::string_view errorContext,
CancellationCallback isCancellationRequested = nullptr) {
struct State {
const std::function<void(const std::string&)>* cb;
const std::function<bool(const std::string&)>* cb;
CancellationCallback isCancellationRequested;
bool cancellationObserved = false;
std::exception_ptr exception;
} state{&onChunk, nullptr};
} state{&onChunk, std::move(isCancellationRequested), false, nullptr};

auto nativeCallback = [](void* data, int32_t len, void* user) -> int {
if (!data || len <= 0)
auto nativeCallback = [](const void* data, int32_t len, void* user) -> int32_t {
auto* st = static_cast<State*>(user);
if (!st) {
return 0;
}

auto* st = static_cast<State*>(user);
if (st->exception)
if (st->exception || st->cancellationObserved) {
return 1;
}

if (!data || len <= 0)
return 0;

try {
if (st->isCancellationRequested && st->isCancellationRequested()) {
st->cancellationObserved = true;
return 1;
}

std::string chunk(static_cast<const char*>(data), static_cast<size_t>(len));
(*(st->cb))(chunk);
if (!(*(st->cb))(chunk)) {
st->cancellationObserved = true;
return 1;
}
}
catch (...) {
st->exception = std::current_exception();
return 1;
}

return 0;
};

auto response = core->call(command, logger, &payload, +nativeCallback, &state);
auto response = core->call(command, logger, payload, +nativeCallback, &state);
if (state.cancellationObserved) {
throw Exception("Operation cancelled", logger);
}

if (response.HasError()) {
throw Exception(std::string(errorContext) + response.error, logger);
}
Expand All @@ -90,6 +137,38 @@ namespace foundry_local::detail {
return response;
}

inline CoreResponse CallWithStreamingCallback(Internal::IFoundryLocalCore* core, std::string_view command,
const std::string* payload, ILogger& logger,
const std::function<void(const std::string&)>& onChunk,
std::string_view errorContext,
CancellationCallback isCancellationRequested = nullptr) {
const std::function<bool(const std::string&)> continuingOnChunk =
[&onChunk](const std::string& chunk) {
onChunk(chunk);
return true;
};
return CallWithStreamingCallback(core, command, payload, logger, continuingOnChunk, errorContext,
std::move(isCancellationRequested));
}

inline CoreResponse CallWithStreamingCallback(Internal::IFoundryLocalCore* core, std::string_view command,
const std::string& payload, ILogger& logger,
const std::function<bool(const std::string&)>& onChunk,
std::string_view errorContext,
CancellationCallback isCancellationRequested = nullptr) {
return CallWithStreamingCallback(core, command, &payload, logger, onChunk, errorContext,
std::move(isCancellationRequested));
}

inline CoreResponse CallWithStreamingCallback(Internal::IFoundryLocalCore* core, std::string_view command,
const std::string& payload, ILogger& logger,
const std::function<void(const std::string&)>& onChunk,
std::string_view errorContext,
CancellationCallback isCancellationRequested = nullptr) {
return CallWithStreamingCallback(core, command, &payload, logger, onChunk, errorContext,
std::move(isCancellationRequested));
}

// Overload: allow Params object directly
inline CoreResponse CallWithParams(Internal::IFoundryLocalCore* core, std::string_view command,
const nlohmann::json& params, ILogger& logger) {
Expand Down
18 changes: 11 additions & 7 deletions sdk/cpp/src/flcore_native.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
#include <cstdint>
#include <type_traits>

#ifdef _WIN32
#define FL_CDECL __cdecl
#else
#define FL_CDECL
#ifndef FL_CDECL
#ifdef _WIN32
#define FL_CDECL __cdecl
#else
#define FL_CDECL
#endif
#endif

extern "C"
Expand All @@ -29,8 +31,9 @@ extern "C"
int32_t ErrorLength;
};

// Callback signature: int(*)(void* data, int length, void* userData) — returns 0 to continue, 1 to cancel
using UserCallbackFn = int(__cdecl*)(void*, int32_t, void*);
// Callback signature: int32_t(*)(const void* data, int length, void* userData)
// Return 0 to continue, 1 to cancel.
using UserCallbackFn = int32_t(FL_CDECL*)(const void*, int32_t, void*);

struct StreamingRequestBuffer {
const void* Command;
Expand All @@ -43,7 +46,8 @@ extern "C"

// Exported function pointer types
using execute_command_fn = void(FL_CDECL*)(RequestBuffer*, ResponseBuffer*);
using execute_command_with_callback_fn = void(FL_CDECL*)(RequestBuffer*, ResponseBuffer*, void* /*callback*/,
using execute_command_with_callback_fn = void(FL_CDECL*)(RequestBuffer*, ResponseBuffer*,
UserCallbackFn /*callback*/,
void* /*userData*/);
using execute_command_with_binary_fn = void(FL_CDECL*)(StreamingRequestBuffer*, ResponseBuffer*);
using free_response_fn = void(FL_CDECL*)(ResponseBuffer*);
Expand Down
Loading
Loading