Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions sdk_v2/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ set(FOUNDRY_LOCAL_SOURCES
src/catalog/azure_catalog_models.cc
src/catalog/catalog_cache.cc
src/catalog/catalog_client.cc
src/catalog/local_model_catalog.cc
src/catalog/local_model_scanner.cc
src/inferencing/generative/audio/audio_generator.cc
src/inferencing/generative/audio/audio_session.cc
Expand Down
49 changes: 48 additions & 1 deletion sdk_v2/cpp/include/foundry_local/foundry_local_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
* Incremented with each release.
* Used to request the API function table via FoundryLocalGetApi.
* ----------------------------------------------------------------------- */
#define FOUNDRY_LOCAL_API_VERSION 1
#define FOUNDRY_LOCAL_API_VERSION 3

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why 1 --> 3 and not 1 --> 2?


/* -----------------------------------------------------------------------
* Platform export macros (C version)
Expand Down Expand Up @@ -202,6 +202,12 @@ typedef enum flDeviceType {
FOUNDRY_LOCAL_DEVICE_NPU = 3
} flDeviceType;

typedef enum flCatalogType {
FOUNDRY_LOCAL_CATALOG_PUBLIC = 0,
FOUNDRY_LOCAL_CATALOG_LOCAL = 1,
FOUNDRY_LOCAL_CATALOG_PRIVATE = 2,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

is private catalog ready yet? if not, I don't think we should allow this option at all.

} flCatalogType;

/// Tensor element data types. Values match ONNX TensorProto.DataType.
typedef enum flTensorDataType {
FOUNDRY_LOCAL_TENSOR_UNDEFINED = 0,
Expand Down Expand Up @@ -256,6 +262,16 @@ typedef enum flTensorDataType {
#define FOUNDRY_LOCAL_MODEL_PROP_TOOL_CALL_END_STR "tool_call_end" ///< optional tool call end marker token
#define FOUNDRY_LOCAL_MODEL_PROP_REASONING_START_STR "reasoning_start" ///< optional reasoning/think start marker token
#define FOUNDRY_LOCAL_MODEL_PROP_REASONING_END_STR "reasoning_end" ///< optional reasoning/think end marker token
#define FOUNDRY_LOCAL_MODEL_PROP_DEVICE_TYPE_STR "device_type" ///< CPU, GPU, or NPU
#define FOUNDRY_LOCAL_MODEL_PROP_EP_STR "execution_provider" ///< optional execution provider
#define FOUNDRY_LOCAL_MODEL_PROP_ENTITY_TYPE_STR "entity_type" ///< fixed to "Model" for BYOM
#define FOUNDRY_LOCAL_MODEL_PROP_AUTHOR_STR "author" ///< optional
#define FOUNDRY_LOCAL_MODEL_PROP_QUANTIZATION_STR "quantization" ///< optional
#define FOUNDRY_LOCAL_MODEL_PROP_CREATION_TIME_STR "creation_time" ///< ISO-8601 UTC timestamp

/* flModelInfo registration properties */
#define FOUNDRY_LOCAL_REG_MODEL_PATH "model_path"
#define FOUNDRY_LOCAL_REG_ALIAS "alias"

/* flModelInfo Int properties. Comments provide details on the type and expected values. */
#define FOUNDRY_LOCAL_MODEL_PROP_SUPPORTS_TOOL_CALLING_INT "supports_tool_calling" ///< optional bool (not set or -1=unknown, 0=false, 1=true)
Expand All @@ -265,6 +281,9 @@ typedef enum flTensorDataType {
#define FOUNDRY_LOCAL_MODEL_PROP_CREATED_AT_UNIX_INT "created_at_unix" ///< Unix timestamp. default=0
#define FOUNDRY_LOCAL_MODEL_PROP_IS_TEST_MODEL_INT "is_test_model" ///< bool (0=false, 1=true)
#define FOUNDRY_LOCAL_MODEL_PROP_CONTEXT_LENGTH_INT "context_length" ///< optional int64_t
#define FOUNDRY_LOCAL_MODEL_PROP_VERSION_INT "version" ///< optional non-negative integer
#define FOUNDRY_LOCAL_MODEL_PROP_FILESIZE_BYTES_INT "file_size_bytes" ///< optional int64_t
#define FOUNDRY_LOCAL_MODEL_PROP_SUPPORTS_HYBRID_REASONING_INT "supports_hybrid_reasoning" ///< optional bool

#define FOUNDRY_LOCAL_MODEL_PROP_INPUT_MODALITIES_STR "input_modalities" ///< optional, comma-separated
#define FOUNDRY_LOCAL_MODEL_PROP_OUTPUT_MODALITIES_STR "output_modalities" ///< optional, comma-separated
Expand Down Expand Up @@ -706,6 +725,12 @@ typedef struct flApi {
bool FL_API_T(Manager_IsShutdownRequested, _In_ const flManager* manager);

// End V1
FL_API_STATUS(Manager_GetCatalogByType, _In_ const flManager* manager, flCatalogType catalog_type,
_Outptr_ flCatalog** out_catalog);
FL_API_STATUS(Manager_GetCatalogByName, _In_ const flManager* manager, _In_ const char* catalog_name,
_Outptr_ flCatalog** out_catalog);

// End V2
/* Append new function pointers at the end for future versions and add marker for the end of each version */
} flApi;

Expand Down Expand Up @@ -969,6 +994,15 @@ struct flCatalogApi {
_In_opt_ const char* model_name, int32_t max_versions, _Outptr_ flModelList** out_models);

// End V1
/// Register a model in a local catalog. The input ModelInfo is copied.
FL_API_STATUS(RegisterModel, _In_ flCatalog* catalog, _In_ const flModelInfo* model_info,
_Outptr_ flModel** out_model);
/// Unregister by alias or model ID without deleting model assets.
FL_API_STATUS(UnregisterModel, _In_ flCatalog* catalog, _In_ const char* alias_or_model_id);
/// List models explicitly registered in this local catalog.
FL_API_STATUS(GetLocalModels, _In_ const flCatalog* catalog, _Outptr_ flModelList** out_models);

// End V2
};

/* --- Model API --------------------------------------------------------- */
Expand Down Expand Up @@ -1038,6 +1072,19 @@ struct flModelApi {
int64_t FL_API_T(Info_GetIntProperty, _In_ const flModelInfo* info, _In_ const char* key, int64_t default_value);

// End V1
/// Create a caller-owned mutable ModelInfo. Release it with ReleaseModelInfo.
FL_API_STATUS(CreateModelInfo, _Outptr_ flModelInfo** out_info);
void FL_API_T(ReleaseModelInfo, _Frees_ptr_opt_ flModelInfo* info);
FL_API_STATUS(Info_SetStringProperty, _In_ flModelInfo* info, _In_ const char* key, _In_ const char* value);
FL_API_STATUS(Info_SetIntProperty, _In_ flModelInfo* info, _In_ const char* key, int64_t value);
FL_API_STATUS(Info_SerializeToFile, _In_ const flModelInfo* info, _In_ const char* file_path);
FL_API_STATUS(Info_DeserializeFromFile, _In_ const char* file_path, _Outptr_ flModelInfo** out_info);

// End V2
/// Create a caller-owned deep copy. Release it with ReleaseModelInfo.
FL_API_STATUS(Info_Clone, _In_ const flModelInfo* info, _Outptr_ flModelInfo** out_info);

// End V3
};

#ifdef __cplusplus
Expand Down
49 changes: 45 additions & 4 deletions sdk_v2/cpp/include/foundry_local/foundry_local_cpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ namespace detail {
/// Returns nullptr if the library does not support the requested API version.
inline const flApi* api() {
static const flApi* p = FoundryLocalGetApi(FOUNDRY_LOCAL_API_VERSION);
if (!p) {
throw std::runtime_error("Foundry Local runtime does not support the API version requested by this header");
}
return p;
}

Expand Down Expand Up @@ -295,14 +298,33 @@ struct Runtime {
std::optional<std::string_view> execution_provider;
};

enum class CatalogType {
Public = FOUNDRY_LOCAL_CATALOG_PUBLIC,
Local = FOUNDRY_LOCAL_CATALOG_LOCAL,
Private = FOUNDRY_LOCAL_CATALOG_PRIVATE,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

is private catalog ready?

};

// ===========================================================================
// ModelInfo — non-owning read-only view
// ModelInfo — owning mutable value or non-owning read-only view
// ===========================================================================

/// Non-owning view over an opaque flModelInfo. Lifetime is tied to the owning Model/Catalog. Immutable.
/// Opaque model metadata. Default construction creates an owning mutable value for registration.
/// Construction from `const flModelInfo&` creates a non-owning read-only view tied to its Model/Catalog.
class ModelInfo {
public:
explicit ModelInfo(const flModelInfo& info) noexcept : info_(&info) {}
ModelInfo();
explicit ModelInfo(const flModelInfo& info) noexcept : handle_(&info) {}

/// Create an independent, owning, mutable deep copy, including when the source is a borrowed view.
ModelInfo(const ModelInfo& other);
ModelInfo& operator=(const ModelInfo& other);
ModelInfo(ModelInfo&&) noexcept = default;
ModelInfo& operator=(ModelInfo&&) noexcept = default;

ModelInfo& SetStringProperty(const char* key, const char* value);
ModelInfo& SetIntProperty(const char* key, int64_t value);
void SerializeToFile(const std::string& file_path) const;
static ModelInfo DeserializeFromFile(const std::string& file_path);

// Core identity.
std::string_view Id() const noexcept;
Expand Down Expand Up @@ -372,8 +394,11 @@ class ModelInfo {
std::optional<std::string_view> Capabilities() const noexcept;

private:
explicit ModelInfo(flModelInfo& info);
static std::string_view safe(const char* s) noexcept { return s ? s : ""; }
const flModelInfo* info_;
detail::Base<flModelInfo> handle_;

friend class Catalog;
};

// ===========================================================================
Expand Down Expand Up @@ -777,6 +802,15 @@ class ICatalog {
virtual ModelList GetModelVersions(const std::string& model_alias,
const std::string& variant_name = {},
int max_versions = 50) = 0;
virtual std::unique_ptr<IModel> RegisterModel(const ModelInfo&) {
throw Error("models can only be registered in a local catalog", FOUNDRY_LOCAL_ERROR_INVALID_ARGUMENT);
}
virtual void UnregisterModel(const std::string&) {
throw Error("models can only be unregistered from a local catalog", FOUNDRY_LOCAL_ERROR_INVALID_ARGUMENT);
}
virtual ModelList GetLocalModels() const {
throw Error("local model listing is unsupported by this catalog", FOUNDRY_LOCAL_ERROR_INVALID_ARGUMENT);
}
};

// ===========================================================================
Expand All @@ -802,6 +836,9 @@ class Catalog final : public ICatalog {
ModelList GetModelVersions(const std::string& model_alias,
const std::string& variant_name = {},
int max_versions = 50) override;
std::unique_ptr<IModel> RegisterModel(const ModelInfo& model_info) override;
void UnregisterModel(const std::string& alias_or_model_id) override;
ModelList GetLocalModels() const override;

private:
detail::Base<flCatalog> handle_;
Expand Down Expand Up @@ -831,6 +868,8 @@ class Manager {

/// Get the catalog for querying models. Creates on first call, caches internally.
ICatalog& GetCatalog() const;
ICatalog& GetCatalog(CatalogType type) const;
ICatalog& GetCatalog(const std::string& catalog_name) const;

/// Start the embedded web service.
void StartWebService();
Expand Down Expand Up @@ -866,7 +905,9 @@ class Manager {
detail::Base<flManager> handle_;
Configuration config_;
mutable std::unique_ptr<Catalog> catalog_;
mutable std::unique_ptr<Catalog> local_catalog_;
mutable std::unique_ptr<std::once_flag> catalog_once_{std::make_unique<std::once_flag>()};
mutable std::unique_ptr<std::once_flag> local_catalog_once_{std::make_unique<std::once_flag>()};
};

// ===========================================================================
Expand Down
114 changes: 102 additions & 12 deletions sdk_v2/cpp/include/foundry_local/foundry_local_cpp.inline.h
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,34 @@ inline ICatalog& Manager::GetCatalog() const {
return *catalog_;
}

inline ICatalog& Manager::GetCatalog(CatalogType type) const {
if (type == CatalogType::Public) {
return GetCatalog();
}

if (type != CatalogType::Local) {
flCatalog* ignored = nullptr;
Check(detail::api()->Manager_GetCatalogByType(handle_.get(), static_cast<flCatalogType>(type), &ignored));
}

std::call_once(*local_catalog_once_, [this, type]() {
flCatalog* catalog = nullptr;
Check(detail::api()->Manager_GetCatalogByType(handle_.get(), static_cast<flCatalogType>(type), &catalog));
local_catalog_ = std::make_unique<Catalog>(*catalog);
});
return *local_catalog_;
}

inline ICatalog& Manager::GetCatalog(const std::string& catalog_name) const {
if (catalog_name == "local") {
return GetCatalog(CatalogType::Local);
}

flCatalog* catalog = nullptr;
Check(detail::api()->Manager_GetCatalogByName(handle_.get(), catalog_name.c_str(), &catalog));
return GetCatalog();
}

inline void Manager::StartWebService() {
Check(detail::api()->Manager_WebServiceStart(handle_.get_mutable()));
}
Expand Down Expand Up @@ -289,32 +317,78 @@ inline flManager* detail::CreateManager(const Configuration& config) {
// ModelInfo
// ===========================================================================

inline ModelInfo::ModelInfo()
: handle_([] {
flModelInfo* info = nullptr;
Check(detail::model_api()->CreateModelInfo(&info));
return info;
}(), detail::model_api()->ReleaseModelInfo) {}

inline ModelInfo::ModelInfo(flModelInfo& info)
: handle_(&info, detail::model_api()->ReleaseModelInfo) {}

inline ModelInfo::ModelInfo(const ModelInfo& other)
: handle_([&other] {
flModelInfo* info = nullptr;
Check(detail::model_api()->Info_Clone(other.handle_.get(), &info));
return info;
}(), detail::model_api()->ReleaseModelInfo) {}

inline ModelInfo& ModelInfo::operator=(const ModelInfo& other) {
if (this != &other) {
ModelInfo clone(other);
*this = std::move(clone);
}

return *this;
}

inline ModelInfo& ModelInfo::SetStringProperty(const char* key, const char* value) {
Check(detail::model_api()->Info_SetStringProperty(handle_.get_mutable(), key, value));
return *this;
}

inline ModelInfo& ModelInfo::SetIntProperty(const char* key, int64_t value) {
Check(detail::model_api()->Info_SetIntProperty(handle_.get_mutable(), key, value));
return *this;
}

inline void ModelInfo::SerializeToFile(const std::string& file_path) const {
Check(detail::model_api()->Info_SerializeToFile(handle_.get(), file_path.c_str()));
}

inline ModelInfo ModelInfo::DeserializeFromFile(const std::string& file_path) {
flModelInfo* info = nullptr;
Check(detail::model_api()->Info_DeserializeFromFile(file_path.c_str(), &info));
return ModelInfo(*info);
}

inline std::string_view ModelInfo::Id() const noexcept {
return safe(detail::model_api()->Info_GetId(info_));
return safe(detail::model_api()->Info_GetId(handle_.get()));
}

inline std::string_view ModelInfo::Name() const noexcept {
return safe(detail::model_api()->Info_GetName(info_));
return safe(detail::model_api()->Info_GetName(handle_.get()));
}

inline int ModelInfo::Version() const noexcept {
return detail::model_api()->Info_GetVersion(info_);
return detail::model_api()->Info_GetVersion(handle_.get());
}

inline std::string_view ModelInfo::Alias() const noexcept {
return safe(detail::model_api()->Info_GetAlias(info_));
return safe(detail::model_api()->Info_GetAlias(handle_.get()));
}

inline std::string_view ModelInfo::Uri() const noexcept {
return safe(detail::model_api()->Info_GetUri(info_));
return safe(detail::model_api()->Info_GetUri(handle_.get()));
}

inline flDeviceType ModelInfo::DeviceType() const noexcept {
return detail::model_api()->Info_GetDeviceType(info_);
return detail::model_api()->Info_GetDeviceType(handle_.get());
}

inline std::optional<std::string_view> ModelInfo::ExecutionProvider() const noexcept {
const char* v = detail::model_api()->Info_GetExecutionProvider(info_);
const char* v = detail::model_api()->Info_GetExecutionProvider(handle_.get());
return v ? std::optional<std::string_view>{v} : std::nullopt;
}

Expand All @@ -327,7 +401,7 @@ inline std::optional<Runtime> ModelInfo::GetRuntime() const noexcept {
}

inline std::optional<std::string_view> ModelInfo::GetPromptTemplate(const char* key) const noexcept {
const flKeyValuePairs* kvps = detail::model_api()->Info_GetPromptTemplates(info_);
const flKeyValuePairs* kvps = detail::model_api()->Info_GetPromptTemplates(handle_.get());
if (!kvps) {
return std::nullopt;
}
Expand All @@ -336,7 +410,7 @@ inline std::optional<std::string_view> ModelInfo::GetPromptTemplate(const char*
}

inline std::optional<std::string_view> ModelInfo::GetModelSetting(const char* key) const noexcept {
const flKeyValuePairs* kvps = detail::model_api()->Info_GetModelSettings(info_);
const flKeyValuePairs* kvps = detail::model_api()->Info_GetModelSettings(handle_.get());
if (!kvps) {
return std::nullopt;
}
Expand All @@ -345,20 +419,20 @@ inline std::optional<std::string_view> ModelInfo::GetModelSetting(const char* ke
}

inline std::optional<KeyValuePairs> ModelInfo::GetModelSettings() const noexcept {
const flKeyValuePairs* kvps = detail::model_api()->Info_GetModelSettings(info_);
const flKeyValuePairs* kvps = detail::model_api()->Info_GetModelSettings(handle_.get());
if (!kvps) {
return std::nullopt;
}
return KeyValuePairs(*kvps);
}

inline std::optional<std::string_view> ModelInfo::GetStringProperty(const char* key) const noexcept {
const char* v = detail::model_api()->Info_GetStringProperty(info_, key);
const char* v = detail::model_api()->Info_GetStringProperty(handle_.get(), key);
return v ? std::optional<std::string_view>{v} : std::nullopt;
}

inline int64_t ModelInfo::GetIntProperty(const char* key, int64_t default_value) const noexcept {
return detail::model_api()->Info_GetIntProperty(info_, key, default_value);
return detail::model_api()->Info_GetIntProperty(handle_.get(), key, default_value);
}

// --- Typed property accessors ---
Expand Down Expand Up @@ -624,6 +698,22 @@ inline ModelList Catalog::GetModelVersions(const std::string& model_alias,
return ModelList(*models);
}

inline std::unique_ptr<IModel> Catalog::RegisterModel(const ModelInfo& model_info) {
flModel* model = nullptr;
Check(detail::catalog_api()->RegisterModel(handle_.get_mutable(), model_info.handle_.get(), &model));
return std::make_unique<Model>(*model);
}

inline void Catalog::UnregisterModel(const std::string& alias_or_model_id) {
Check(detail::catalog_api()->UnregisterModel(handle_.get_mutable(), alias_or_model_id.c_str()));
}

inline ModelList Catalog::GetLocalModels() const {
flModelList* models = nullptr;
Check(detail::catalog_api()->GetLocalModels(handle_.get(), &models));
return ModelList(*models);
}

// ===========================================================================
// Item
// ===========================================================================
Expand Down
Loading
Loading