Skip to content

Separately addressable catalogs (C++ SDK) - #943

Open
kobby-kobbs wants to merge 14 commits into
mainfrom
emmanuel/separate-catalogs
Open

Separately addressable catalogs (C++ SDK)#943
kobby-kobbs wants to merge 14 commits into
mainfrom
emmanuel/separate-catalogs

Conversation

@kobby-kobbs

Copy link
Copy Markdown

Summary

Adds support for multiple, separately addressable named catalogs in the Foundry Local C++ SDK. Instead of a single catalog (or a merged/aggregated one), the Manager now holds N catalogs, each registered under a unique name and each backed by its own AzureModelCatalog. Results are never unioned or de-duplicated across catalogs, you address one catalog at a time.

Motivation

Consumers need to register more than one model source and query each independently (e.g. a public catalog plus a private/org catalog) without the SDK merging their contents.

What changed

Config API — Configuration::AddCatalog(name, url, filter?) registers a named catalog. AddCatalogUrl(url) remains supported and registers the catalog under an auto-derived name (its URL).

Manager — holds one AzureModelCatalog per source. GetCatalog(name) resolves a specific catalog; ListCatalogNames() enumerates registered names in add-order. The no-arg GetCatalog() returns the first-registered (default) catalog; with no source added, the built-in Azure Foundry catalog is registered under the reserved name "public" and serves as the default.

C ABI — additive, versioned: Manager_GetCatalogByName and Manager_ListCatalogNames (appended before the // End V1 marker). Unknown name → FOUNDRY_LOCAL_ERROR_INVALID_ARGUMENT.

C++ wrapper — ergonomic GetCatalog(name) / ListCatalogNames() mirroring the C ABI, with per-name wrapper caching.
Tests — C ABI and C++ wrapper coverage for registration, listing/order, name resolution + caching, default behavior, and error cases.

Docs — updated CppPortGuide.md (catalog architecture) and WrapperInterfacesDesign.md, plus header doc comments.
Example — a new interactive catalog_example REPL (list/add/use/name/models) for hands-on exploration.

Design notes

N separately addressable catalogs; no aggregation/union/de-duplication/origin-tagging.
Default catalog = first registered, or built-in "public" when none added; "public" is reserved.
AzureModelCatalog signature is unchanged; the Manager's name→catalog map is the authoritative key.
C ABI change is purely additive to preserve ABI compatibility.

Emmanuel Assumang added 8 commits July 28, 2026 12:04
Introduce named catalog sources so multiple catalogs can be separately
addressed:
- Add CatalogSource{name,url,filter} struct and kDefaultCatalogName="public"
  in configuration.h; change catalog_urls to vector<CatalogSource>.
- Validate() now rejects empty catalog name and url.
- New AddCatalog(name,url,filter) across the C ABI (foundry_local_c.h /
  c_api.cc vtable) and the C++ wrapper (foundry_local_cpp.h / .inline.h);
  AddCatalogUrl auto-derives the name from the URL.
- manager.cc shim converts named sources back to pairs for the existing
  AzureModelCatalog.
- Tests: add reject-empty-name; update existing configuration tests to
  CatalogSource.
Replace the single catalog_ member with an add-ordered list of named
catalogs. Build one AzureModelCatalog per registered source instead of
one merged catalog; when none are registered, expose the built-in Azure
Foundry catalog as the default "public" catalog.

- manager.h: NamedCatalog{name,catalog} vector; add GetCatalog(name)
  overload and ListCatalogNames().
- manager.cc: construct one catalog per source; GetCatalog() returns the
  first (default) catalog, GetCatalog(name) looks up by name and throws
  INVALID_ARGUMENT if unknown, ListCatalogNames() enumerates in add-order.
  Web service uses GetCatalog(); InvalidateCache loops all catalogs.

Default catalog = first registered ("public" when none added). No
aggregation across catalogs.
Add Manager_GetCatalogByName and Manager_ListCatalogNames to the C ABI so
callers can address individual registered catalogs by name.

- foundry_local_c.h: append the two functions before the V1 end marker.
- c_api.cc: cache per-name flCatalog wrappers on flManager so returned
  pointers stay valid for the manager's lifetime; back Manager_ListCatalogNames
  with owned string storage. Register both in the g_api_v1 vtable.

Manager_GetCatalog continues to return the default catalog.
Add Manager::GetCatalog(name) and Manager::ListCatalogNames() to the RAII
C++ wrapper, layered over the C ABI added previously.

- foundry_local_cpp.h: declare both methods; cache named Catalog wrappers
  in a mutex-guarded map so repeated lookups return the same object.
- foundry_local_cpp.inline.h: implement both over Manager_GetCatalogByName
  and Manager_ListCatalogNames.

The no-argument GetCatalog() continues to return the default catalog.
Cover the named-catalog surface across the C ABI and the C++ wrapper:

- C ABI (c_api_test.cc): Manager_ListCatalogNames defaults to "public",
  returns registered names in add-order, AddCatalogUrl auto-derives the
  name from the URL; Manager_GetCatalogByName resolves registered catalogs
  and caches handles, fails with INVALID_ARGUMENT for unknown/null names;
  null-argument validation for both entry points.
- C++ wrapper (cpp_api_test.cc): Configuration::AddCatalog chaining,
  Manager::ListCatalogNames default and ordered results, GetCatalog(name)
  resolution vs. the default GetCatalog(), and throwing on unknown names.
Update the C++ SDK docs and public header comments to describe the
separately addressable named catalogs:

- CppPortGuide: rewrite the catalog-architecture note to explain N named
  catalogs (no aggregation/union/de-dup), the AddCatalog/AddCatalogUrl
  config surface, GetCatalog(name)/ListCatalogNames, and the default
  ("public") catalog behavior.
- WrapperInterfacesDesign: mention the GetCatalog(name) and
  ListCatalogNames overloads.
- foundry_local_c.h / foundry_local_cpp.h: clarify that the no-argument
  GetCatalog returns the first-registered (or built-in "public") catalog.
A runnable example that links the SDK and exercises the multi-catalog
surface without network access or model downloads:

- default catalog behavior when no source is added (registered as "public")
- registering several named catalogs and enumerating them with
  ListCatalogNames
- resolving each catalog by name and the cached repeated-lookup behavior
- the default catalog corresponding to the first registered name
- the Error raised for an unknown catalog name

Wired into the examples build as catalog_example.
Turn the catalog example into a small REPL for hands-on exploration of the
named multi-catalog surface:

- list                 enumerate registered catalog names
- add <name> <url>     register a named catalog (rebuilds the singleton manager)
- use <name>           select the current catalog (validates the name)
- name                 show the current catalog's reported name
- models               live-query the current catalog's models
- help / quit

The manager is a process-wide singleton, so adding a catalog destroys the
existing manager before constructing a new one from the updated sources.
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
foundry-local Ready Ready Preview Aug 4, 2026 6:23pm

Request Review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds independently addressable named catalogs to the C++ SDK.

Changes:

  • Adds named catalog configuration, lookup, enumeration, and C/C++ APIs.
  • Refactors Manager to own multiple catalogs and adds tests.
  • Adds documentation and an interactive catalog example.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
sdk_v2/cpp/CMakeLists.txt Builds the catalog example.
sdk_v2/cpp/docs/CppPortGuide.md Documents multi-catalog architecture.
sdk_v2/cpp/docs/WrapperInterfacesDesign.md Documents wrapper APIs.
sdk_v2/cpp/examples/catalog/CMakeLists.txt Defines the example target.
sdk_v2/cpp/examples/catalog/main.cc Implements the catalog REPL.
sdk_v2/cpp/include/foundry_local/foundry_local_c.h Extends C API tables.
sdk_v2/cpp/include/foundry_local/foundry_local_cpp.h Declares C++ APIs and caches.
sdk_v2/cpp/include/foundry_local/foundry_local_cpp.inline.h Implements C++ wrappers.
sdk_v2/cpp/src/c_api.cc Implements C ABI operations.
sdk_v2/cpp/src/configuration.cc Validates catalog sources.
sdk_v2/cpp/src/configuration.h Models named catalog sources.
sdk_v2/cpp/src/manager.cc Creates and resolves multiple catalogs.
sdk_v2/cpp/src/manager.h Declares multi-catalog storage.
sdk_v2/cpp/test/internal_api/c_api_test.cc Tests the C ABI.
sdk_v2/cpp/test/internal_api/configuration_test.cc Tests configuration validation.
sdk_v2/cpp/test/sdk_api/cpp_api_test.cc Tests the public C++ API.

Comment on lines +45 to +50
for (const auto& source : catalog_urls) {
if (source.url.empty()) {
FL_THROW(FOUNDRY_LOCAL_ERROR_INVALID_ARGUMENT, "Configuration: catalog URL must not be empty");
}
if (source.name.empty()) {
FL_THROW(FOUNDRY_LOCAL_ERROR_INVALID_ARGUMENT, "Configuration: catalog name must not be empty");
Comment on lines +936 to +937
FL_API_STATUS(AddCatalog, _In_ flConfiguration* config, _In_ const char* name, _In_ const char* url,
_In_opt_ const char* filter_override);
Comment thread sdk_v2/cpp/src/manager.cc
Comment on lines +334 to +336
return std::make_unique<AzureModelCatalog>(
std::move(urls),
download_manager_->GetCacheDirectory(),
Comment on lines +214 to +220
inline ICatalog& Manager::GetCatalog(const std::string& name) const {
std::lock_guard<std::mutex> lock(*named_catalogs_mutex_);
auto it = named_catalogs_.find(name);
if (it == named_catalogs_.end()) {
flCatalog* cat = nullptr;
Check(detail::api()->Manager_GetCatalogByName(handle_.get(), name.c_str(), &cat));
it = named_catalogs_.emplace(name, std::unique_ptr<Catalog>(new Catalog(*cat))).first;
Comment thread sdk_v2/cpp/include/foundry_local/foundry_local_cpp.inline.h
Comment thread sdk_v2/cpp/include/foundry_local/foundry_local_c.h Outdated
Comment thread sdk_v2/cpp/src/manager.h Outdated
Comment thread sdk_v2/cpp/src/manager.h Outdated
kobby-kobbs and others added 4 commits August 3, 2026 17:16
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
- configuration: reject reserved ("public") and duplicate catalog names in
  Validate(), with tests covering both cases.
- C ABI: move AddCatalog to the end of flConfigurationApi (append-only rule) so
  clients compiled against the previous vtable keep dispatching correctly.
- C++ wrapper: route the no-arg GetCatalog() through the named cache so the
  default catalog resolves to a single canonical wrapper shared by both paths.
- catalog cache: namespace the per-catalog metadata snapshot by catalog
  identity (URL/filter) while keeping the shared model-blob cache directory, so
  separately addressable catalogs no longer collide on foundry.modelinfo.json.
  The default ("public") catalog keeps the canonical file name.
- c_api.cc: fully initialize the flManager aggregate in Manager_CreateImpl.
  Three new members (catalog_by_name, catalog_names_storage,
  catalog_names_cache) were left out of the brace-init, which trips gcc's
  -Wmissing-field-initializers (-Wextra), promoted to an error by -Werror on
  the Linux build. MSVC (/EHsc only) ignored it, so local Windows builds
  passed.

- Move the named-catalog tests (default-catalog, resolve-by-name,
  unknown-name-throws) out of cpp_api_test.cc (sdk_integration_tests binary,
  whose SharedTestEnv holds the process-wide Manager singleton) and into
  catalog_live_test.cc (cache_only_tests binary, no SharedTestEnv). Each test
  constructs its own Manager, which threw "Manager already created" in the
  old binary. Tests use a temp cache dir and are metadata-only (no network).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants