Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
29a1bad
the implementation for BYOM
selenayang888 Jul 30, 2026
15e1dde
Add deep-copy semantics for ModelInfo
selenayang888 Aug 6, 2026
f1cb59a
Resolve conflicts
selenayang888 Aug 6, 2026
c1f1b8b
Fix the comment about "Deferred registrations"
selenayang888 Aug 7, 2026
0c6a729
fixed execution-provider override bug
selenayang888 Aug 7, 2026
a2dd9b2
Merge remote-tracking branch 'origin/main' into syang/bring-your-own-…
selenayang888 Aug 8, 2026
a1d7854
Addressed the comments
selenayang888 Aug 12, 2026
398f702
Added byom e2e test
selenayang888 Aug 12, 2026
75314ca
Guard config-derived EPs for BYOM model loads
selenayang888 Aug 13, 2026
37bfd13
Remove redundant GetLocalModels catalog API
selenayang888 Aug 14, 2026
46c523a
Merge remote-tracking branch 'origin/main' into syang/bring-your-own-…
selenayang888 Aug 14, 2026
db04e30
Removed the private catalog
selenayang888 Aug 14, 2026
fe1a713
Address high-confidence BYOM review feedback
selenayang888 Aug 15, 2026
49ae75c
Use one <alias>:<version> ID instead of separate values; Use filesize…
selenayang888 Aug 18, 2026
8843b9a
Extract reusable UTC timestamp utility
selenayang888 Aug 18, 2026
e3f0edb
Resolved several comments
selenayang888 Aug 19, 2026
43db1a0
resolve the comments
selenayang888 Aug 19, 2026
729e08a
Resolve "A constructed model should never have missing metadata"
selenayang888 Aug 20, 2026
49ecc8b
Name local model factory arguments
selenayang888 Aug 20, 2026
488aff7
Merge remote-tracking branch 'origin/main' into syang/bring-your-own-…
selenayang888 Aug 20, 2026
5591379
Fix BYOM catalog tests and Python API v2 bindings
selenayang888 Aug 20, 2026
5483fc9
Merge remote-tracking branch 'origin/main' into syang/bring-your-own-…
selenayang888 Aug 20, 2026
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
2 changes: 2 additions & 0 deletions sdk_v2/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,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 Expand Up @@ -270,6 +271,7 @@ set(FOUNDRY_LOCAL_SOURCES
src/util/path_safety.cc
src/util/region_fallback.cc
src/util/sha256.cc
src/util/time_utils.cc
src/util/zip_extract.cc
${FOUNDRY_LOCAL_PLATFORM_SOURCES}
${FOUNDRY_LOCAL_INTERNAL_HEADERS}
Expand Down
48 changes: 43 additions & 5 deletions 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 2

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

typedef enum flCatalogType {
FOUNDRY_LOCAL_CATALOG_PUBLIC = 0,
FOUNDRY_LOCAL_CATALOG_LOCAL = 1,
} flCatalogType;

/// Tensor element data types. Values match ONNX TensorProto.DataType.
typedef enum flTensorDataType {
FOUNDRY_LOCAL_TENSOR_UNDEFINED = 0,
Expand Down Expand Up @@ -256,6 +261,12 @@ 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 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 +276,7 @@ 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_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 @@ -709,6 +721,10 @@ 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);

// 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 @@ -941,8 +957,9 @@ struct flCatalogApi {
/// Returned string is owned by the catalog and valid for the catalog's lifetime.
FL_API_STATUS(GetName, _In_ const flCatalog* catalog, _Out_ const char** out_name);

// Catalog owns model list. Cached for efficiency.
// Models are mutable for load/unload/remove operations. Model info is immutable though.
/// The caller owns each returned model list and must release it with ModelList_Release. The model handles in a list
/// are borrowed from the catalog and remain address-valid until the owning manager is destroyed. Releasing a list
/// does not invalidate its model handles. Models are mutable for load/unload/remove operations; model info is immutable.
FL_API_STATUS(GetModels, _In_ const flCatalog* catalog, _Outptr_ flModelList** out_models);
FL_API_STATUS(GetModel, _In_ const flCatalog* catalog, _In_ const char* alias,
_Outptr_ flModel** out_model);
Expand Down Expand Up @@ -972,6 +989,20 @@ 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 without taking ownership of its assets.
/// `model_path` must identify a model directory containing genai_config.json.
/// `model_id` must use the canonical `<name>:<version>` format and be unique in the local catalog.
/// The metadata is copied; model identity and location are taken only from the explicit arguments.
FL_API_STATUS(RegisterModel, _In_ flCatalog* catalog, _In_ const char* model_path,
_In_ const char* model_id, _In_ const flModelInfo* metadata,
_Outptr_ flModel** out_model);
/// Unregister by alias or model ID without deleting model assets.
/// Future catalog queries exclude the registration, but outstanding model handles and their immutable metadata remain
/// valid until the owning manager is destroyed. Operations that require the retired registration, including Download
/// and Load, return FOUNDRY_LOCAL_ERROR_INVALID_USAGE; query and cleanup operations such as Unload remain valid.
FL_API_STATUS(UnregisterModel, _In_ flCatalog* catalog, _In_ const char* alias_or_model_id);

// End V2
};

/* --- Model API --------------------------------------------------------- */
Expand All @@ -988,8 +1019,8 @@ struct flModelApi {

/* Model handle operations. Catalog owns Model instances. */
FL_API_STATUS(IsCached, _In_ const flModel* model, _Out_ int* out_cached);
/// Returned path string is owned by the model and valid until the model is released or its cache state
/// changes via RemoveFromCache.
/// Returned path string is owned by the model and valid until the owning manager is destroyed or the model's cache
/// state changes via Download or RemoveFromCache.
FL_API_STATUS(GetPath, _In_ const flModel* model, _Out_ const char** out_path);
FL_API_STATUS(Download, _In_ flModel* model, _In_opt_ flProgressCallback callback,
_In_opt_ void* user_data);
Expand Down Expand Up @@ -1041,6 +1072,13 @@ 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);

// End V2
};

#ifdef __cplusplus
Expand Down
63 changes: 54 additions & 9 deletions sdk_v2/cpp/include/foundry_local/foundry_local_cpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,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 @@ -298,14 +301,31 @@ struct Runtime {
std::optional<std::string_view> execution_provider;
};

enum class CatalogType {
Public = FOUNDRY_LOCAL_CATALOG_PUBLIC,
Local = FOUNDRY_LOCAL_CATALOG_LOCAL,
};

// ===========================================================================
// 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.
class ModelInfo {
public:
explicit ModelInfo(const flModelInfo& info) noexcept : info_(&info) {}
ModelInfo();
explicit ModelInfo(const flModelInfo& info) noexcept : handle_(&info) {}

ModelInfo(const ModelInfo&) = delete;
ModelInfo& operator=(const ModelInfo&) = delete;
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);

const flModelInfo* native_handle() const noexcept { return handle_.get(); }

// Core identity.
std::string_view Id() const noexcept;
Expand Down Expand Up @@ -376,7 +396,7 @@ class ModelInfo {

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

// ===========================================================================
Expand Down Expand Up @@ -695,6 +715,9 @@ class IModel {
// Model — concrete IModel implementation using composition
// ===========================================================================

/// Non-owning wrapper over a catalog-owned model. The Manager that supplied the catalog must outlive this object and
/// any ModelInfo view obtained from it. Unregistering a local model removes it from future catalog queries without
/// invalidating existing wrappers; operations that require the retired registration, including Download and Load, fail.
class Model final : public IModel {
public:
/// Mutable construction (from catalog lookups that return flModel*).
Expand Down Expand Up @@ -735,6 +758,8 @@ class Model final : public IModel {
// ModelList
// ===========================================================================

/// Owning wrapper for a native model-list allocation. Its Model entries are non-owning views into catalog storage, so
/// the Manager that supplied the catalog must outlive the list and any Model wrapper retained from it.
class ModelList {
public:
ModelList(flModelList& model_list);
Expand Down Expand Up @@ -780,12 +805,22 @@ class ICatalog {
/// returns every variant. `max_versions` selects the latest X versions per
/// variant name (defaults to 50, matching the web service contract); pass 0
/// or a negative value for no per-variant cap. Each call performs a fresh
/// query and the returned model handles remain valid until the next
/// GetModelVersions call for the same alias or until the catalog is destroyed.
/// Queries for different aliases do not invalidate each other's results.
/// query and the returned model handles remain valid until the owning Manager
/// is destroyed. Repeated queries do not invalidate earlier results.
virtual ModelList GetModelVersions(const std::string& model_alias,
const std::string& variant_name = {},
int max_versions = 50) = 0;

/// Register existing local model assets. `model_id` must use `<name>:<version>`; metadata is copied.
/// The catalog does not take ownership of `model_path` and never deletes its contents.
virtual std::unique_ptr<IModel> RegisterModel(const std::string&, const std::string&, const ModelInfo&) {
throw Error("models can only be registered in a local catalog", FOUNDRY_LOCAL_ERROR_INVALID_ARGUMENT);
}
/// Unregister without deleting model assets. Existing model wrappers and immutable metadata views remain valid, but
/// operations that require the retired registration, including Download and Load, fail with invalid usage.
virtual void UnregisterModel(const std::string&) {

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.

Suggest "Deregister"

@selenayang888 Selena Yang (selenayang888) Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We retained UnregisterModel/UnregisterModel pair and precisely describes removing the catalog registration without deleting the model assets. If there is a project-wide naming convention requiring DeregisterModel, I can apply it consistently across the C and C++ APIs before merge.

throw Error("models can only be unregistered from a local catalog", FOUNDRY_LOCAL_ERROR_INVALID_ARGUMENT);
}
};

// ===========================================================================
Expand All @@ -794,7 +829,7 @@ class ICatalog {

class Catalog final : public ICatalog {
public:
/// Adopt an already-created catalog handle (owning).
/// Wrap an already-created manager-owned catalog handle (non-owning).
/// Most users should obtain a catalog via Manager::GetCatalog() rather than constructing one directly.
explicit Catalog(flCatalog& catalog) : handle_(&catalog) {}

Expand All @@ -811,6 +846,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 std::string& model_path, const std::string& model_id,
const ModelInfo& metadata) override;
void UnregisterModel(const std::string& alias_or_model_id) override;

private:
detail::Base<flCatalog> handle_;
Expand Down Expand Up @@ -840,6 +878,7 @@ class Manager {

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

/// Start the embedded web service.
void StartWebService();
Expand Down Expand Up @@ -872,10 +911,16 @@ class Manager {
bool IsShutdownRequested() const;

private:
struct CatalogCollection {
mutable std::unique_ptr<Catalog> public_;
mutable std::unique_ptr<Catalog> local_;
};

detail::Base<flManager> handle_;
Configuration config_;
mutable std::unique_ptr<Catalog> catalog_;
CatalogCollection catalogs_;
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
Loading
Loading