Repository navigation
Expand file tree
/
Copy pathfoundry_local_cpp.h
More file actions
1270 lines (1033 loc) · 52.1 KB
/
Copy pathfoundry_local_cpp.h
File metadata and controls
1270 lines (1033 loc) · 52.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
/// @file foundry_local_cpp.h
/// Header-only C++ wrapper over the Foundry Local C API.
/// External consumers include this + link against the shared library.
///
/// This file contains class declarations only. All method implementations
/// are in foundry_local_cpp.inline.h, included at the end.
#pragma once
// This header requires C++17 or later.
// The sdk_integration_test target builds as C++17 to enforce this.
#if defined(__cplusplus) && __cplusplus < 201703L && !defined(_MSVC_LANG)
#error "foundry_local_cpp.h requires C++17 or later."
#elif defined(_MSVC_LANG) && _MSVC_LANG < 201703L
#error "foundry_local_cpp.h requires C++17 or later."
#endif
#ifdef GetMessage
// Win32 maps GetMessage to GetMessageA/GetMessageW. The Foundry C++ API owns
// this name and must remain independent of the consumer's Windows include order.
#undef GetMessage
#endif
#include "foundry_local/foundry_local_c.h"
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <memory>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
namespace foundry_local {
// ===========================================================================
// Error handling
// ===========================================================================
/// Exception thrown when a C API call fails.
class Error : public std::runtime_error {
public:
Error(flStatus& status);
Error(std::string_view message, flErrorCode code);
flErrorCode Code() const noexcept { return code_; }
private:
static const char* GetErrorMessage(flStatus& status);
flErrorCode code_;
};
/// Check a C API status; throw Error if non-null (non-null = error).
inline void Check(flStatus* status);
/// Get the library version string.
inline const char* Version() noexcept;
namespace detail {
inline uint32_t api_version() {
static const uint32_t version = FoundryLocalGetApi(FOUNDRY_LOCAL_API_VERSION) ? FOUNDRY_LOCAL_API_VERSION : 2;
return version;
}
/// Get the API function table (cached on first call). API v2 remains supported for non-hosted operations.
inline const flApi* api() {
static const flApi* p = FoundryLocalGetApi(api_version());
if (!p) {
throw std::runtime_error("Foundry Local runtime does not support the API version requested by this header");
}
return p;
}
/// Get the Catalog sub-API (cached on first call).
inline const flCatalogApi* catalog_api() {
static const flCatalogApi* p = api()->GetCatalogApi();
return p;
}
/// Get the Configuration sub-API (cached on first call).
inline const flConfigurationApi* config_api() {
static const flConfigurationApi* p = api()->GetConfigurationApi();
return p;
}
/// Get the Inference sub-API (cached on first call).
inline const flInferenceApi* inference_api() {
static const flInferenceApi* p = api()->GetInferenceApi();
return p;
}
/// Get the Item sub-API (cached on first call).
inline const flItemApi* item_api() {
static const flItemApi* p = api()->GetItemApi();
return p;
}
/// Get the Model sub-API (cached on first call).
inline const flModelApi* model_api() {
static const flModelApi* p = api()->GetModelApi();
return p;
}
/// RAII handle for opaque C types. Supports dual mutable/const pointer design.
/// Mutable construction stores both pointers and optionally owns the resource.
/// Const construction stores only the read-only pointer (non-owning view).
template <typename T>
class Base {
public:
// Mutable construction — both pointers set. Optionally owning (release_fn).
explicit Base(T* p, std::function<void(T*)> release_fn = nullptr) noexcept
: const_p_(p), mutable_p_(p), release_fn_(std::move(release_fn)) {}
// Const construction — read-only view, never owns.
explicit Base(const T* p) noexcept : const_p_(p) {}
~Base() {
if (mutable_p_ && release_fn_) {
release_fn_(mutable_p_);
}
}
Base(const Base&) = delete;
Base& operator=(const Base&) = delete;
Base(Base&& v) noexcept
: const_p_(v.const_p_), mutable_p_(v.mutable_p_), release_fn_(std::move(v.release_fn_)) {
v.const_p_ = nullptr;
v.mutable_p_ = nullptr;
}
Base& operator=(Base&& v) noexcept {
if (this != &v) {
if (mutable_p_ && release_fn_) {
release_fn_(mutable_p_);
}
const_p_ = v.const_p_;
mutable_p_ = v.mutable_p_;
release_fn_ = std::move(v.release_fn_);
v.const_p_ = nullptr;
v.mutable_p_ = nullptr;
}
return *this;
}
const T* get() const noexcept { return const_p_; }
T* get_mutable() {
if (!mutable_p_) {
throw Error("Mutable access on a read-only handle", FOUNDRY_LOCAL_ERROR_INVALID_USAGE);
}
return mutable_p_;
}
bool is_mutable() const noexcept { return mutable_p_ != nullptr; }
void detach() noexcept {
const_p_ = nullptr;
mutable_p_ = nullptr;
release_fn_ = nullptr;
}
private:
const T* const_p_{};
T* mutable_p_{};
std::function<void(T*)> release_fn_{};
};
} // namespace detail
// ===========================================================================
// Forward declarations
// ===========================================================================
class IModel;
class ICatalog;
class Catalog;
class Model;
class ModelList;
class Item;
struct InputOutputInfo;
// ===========================================================================
// KeyValuePairs
// ===========================================================================
/// A key/value entry.
struct KeyValueEntry {
std::string_view key;
std::string_view value;
};
/// Unified key/value pair wrapper. Supports both owning (mutable) and non-owning (read-only) modes.
class KeyValuePairs {
public:
/// Create new empty (mutable, owning).
KeyValuePairs();
/// Create from initializer list (mutable, owning).
KeyValuePairs(std::initializer_list<std::pair<const char*, const char*>> pairs);
/// Read-only view (non-owning).
explicit KeyValuePairs(const flKeyValuePairs& p) : handle_(&p) {}
/// Mutable non-owning (e.g. from GetMutableMetadata).
explicit KeyValuePairs(flKeyValuePairs& p) : handle_(&p) {}
KeyValuePairs(KeyValuePairs&&) noexcept = default;
KeyValuePairs& operator=(KeyValuePairs&&) noexcept = default;
/// Get a value by key. Returns nullopt if not found.
std::optional<std::string_view> Get(const char* key) const noexcept;
/// Get all key/value pairs.
std::vector<KeyValueEntry> GetAll() const;
/// Native handle accessor (const).
const flKeyValuePairs* native_handle() const noexcept { return handle_.get(); }
/// Add or replace a key/value pair. Throws if read-only.
KeyValuePairs& Set(const char* key, const char* value);
/// Remove a key. Throws if read-only.
KeyValuePairs& Remove(const char* key);
private:
detail::Base<flKeyValuePairs> handle_;
};
// ===========================================================================
// Configuration
// ===========================================================================
class Configuration {
public:
explicit Configuration(const std::string& app_name);
Configuration(Configuration&&) noexcept = default;
Configuration& operator=(Configuration&&) noexcept = default;
/// Optional. Directory for Foundry Local SDK app data. Defaults to ~/.<app_name>.
/// Use `{home}` as a placeholder for the user's home directory.
Configuration& SetAppDataDir(const std::string& value);
/// Optional. Directory for log files. Defaults to <app_data_dir>/logs.
Configuration& SetLogsDir(const std::string& value);
/// Optional. Directory for cached models. Defaults to <app_data_dir>/cache/models.
Configuration& SetModelCacheDir(const std::string& value);
/// Optional. Default log level. Defaults to Warning.
Configuration& SetDefaultLogLevel(flLogLevel level);
/// Optional. Add a catalog URL to connect to.
/// Defaults to the Azure Foundry Local Catalog if none are added.
/// Legacy Azure `https://ai.azure.com/.../ux/v1.0` URLs are unsupported. Compatible custom
/// catalog endpoints may use a different path.
/// Prefix catalog-v2 deployment-option overrides with `deploymentOptions=`. Unqualified
/// overrides retain the legacy `foundryLocal` tag semantics.
Configuration& AddCatalogUrl(const std::string& url,
const std::optional<std::string>& filter_override = std::nullopt);
/// Optional. Add an endpoint for the web service to bind to.
/// Defaults to "http://127.0.0.1:0" (ephemeral port) if none are added.
Configuration& AddWebServiceEndpoint(const std::string& url);
/// Optional. Set additional/undocumented options as key/value pairs.
/// These are passed through to the core implementation.
Configuration& SetAdditionalOptions(const KeyValuePairs& options);
/// Optional. URL of an external Foundry Local service for client-only mode.
/// When set, the catalog reads only from the local disk cache and local-only
/// operations (StartWebService, session creation) return errors.
Configuration& SetExternalServiceUrl(const std::string& url);
/// Optional. Use the packaged native shared host instead of in-process execution. Defaults to false.
/// Requires an API v3 runtime when enabled. Cannot be combined with SetExternalServiceUrl.
Configuration& SetUseSharedHost(bool enabled);
/// Optional. Azure region for the model registry download endpoint.
/// Defaults to "centralus" when not set.
Configuration& SetCatalogRegion(const std::string& region);
/// Optional. Disable non-essential telemetry. Foundry Local may still send a minimal ProcessInfo event.
/// Defaults to false (telemetry enabled).
Configuration& SetDisableNonessentialTelemetry(bool disable);
const flConfiguration* native_handle() const noexcept { return handle_.get(); }
private:
detail::Base<flConfiguration> handle_;
};
// ===========================================================================
// EpInfo — execution provider discovery result
// ===========================================================================
/// Information about a discoverable execution provider.
struct EpInfo {
std::string name; ///< EP name (e.g. "CUDAExecutionProvider")
bool is_registered; ///< Whether this EP has been successfully registered
};
// ===========================================================================
// Runtime — device + execution provider pair
// ===========================================================================
/// Runtime info for a model variant: where it runs (device + execution provider).
struct Runtime {
flDeviceType device_type;
std::optional<std::string_view> execution_provider;
};
// ===========================================================================
// ModelInfo — owning mutable value or non-owning read-only view
// ===========================================================================
/// 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:
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;
std::string_view Name() const noexcept;
int Version() const noexcept;
std::string_view Alias() const noexcept;
std::string_view Uri() const noexcept;
flDeviceType DeviceType() const noexcept;
std::optional<std::string_view> ExecutionProvider() const noexcept;
/// Returns the device + execution provider pair, or nullopt if device_type is unknown/invalid.
std::optional<Runtime> GetRuntime() const noexcept;
// Key-value lookups
std::optional<std::string_view> GetPromptTemplate(const char* key) const noexcept;
std::optional<std::string_view> GetModelSetting(const char* key) const noexcept;
/// Default/recommended inference settings declared by the model author.
/// Returns a non-owning read-only view; nullopt if no settings are declared.
/// Useful for discovering which parameters a model supports overriding.
std::optional<KeyValuePairs> GetModelSettings() const noexcept;
/// Get a string property by key. Known keys are defined by the FOUNDRY_LOCAL_MODEL_PROP_*_STR constants.
std::optional<std::string_view> GetStringProperty(const char* key) const noexcept;
/// Get an int property by key. Known keys are defined by the FOUNDRY_LOCAL_MODEL_PROP_*_INT constants.
/// Returns default_value if key is not set.
int64_t GetIntProperty(const char* key, int64_t default_value = -1) const noexcept;
// --- Typed property accessors (convenience wrappers over Get{String,Int}Property) ---
/// Display name shown in UIs. May differ from name().
std::optional<std::string_view> DisplayName() const noexcept;
/// Model format, e.g. "onnx".
std::optional<std::string_view> ModelType() const noexcept;
/// Publisher / organization.
std::optional<std::string_view> Publisher() const noexcept;
/// SPDX license identifier.
std::optional<std::string_view> License() const noexcept;
/// Human-readable license description.
std::optional<std::string_view> LicenseDescription() const noexcept;
/// Task the model is designed for, e.g. "chat", "text-generation".
std::optional<std::string_view> Task() const noexcept;
/// Source of the model, e.g. "AzureCatalog".
std::optional<std::string_view> ModelProvider() const noexcept;
/// Minimum Foundry Local version required to run this model.
std::optional<std::string_view> MinFlVersion() const noexcept;
/// URI of the parent model (for derived/quantized variants).
std::optional<std::string_view> ParentUri() const noexcept;
/// Whether the model supports tool/function calling. nullopt if unspecified.
std::optional<bool> SupportsToolCalling() const noexcept;
/// Download size in megabytes. nullopt if unspecified.
std::optional<int64_t> FilesizeMb() const noexcept;
/// Maximum output tokens the model can generate. nullopt if unspecified.
std::optional<int64_t> MaxOutputTokens() const noexcept;
/// Unix timestamp when the model entry was created. 0 if unset.
int64_t CreatedAtUnix() const noexcept;
/// Whether this is a test/synthetic model (not for production).
bool IsTestModel() const noexcept;
/// Maximum context length in tokens. nullopt if unspecified.
std::optional<int64_t> ContextLength() const noexcept;
/// Comma-separated list of supported input modalities (e.g. "text,image"). nullopt if unspecified.
std::optional<std::string_view> InputModalities() const noexcept;
/// Comma-separated list of supported output modalities. nullopt if unspecified.
std::optional<std::string_view> OutputModalities() const noexcept;
/// Comma-separated list of model capabilities. nullopt if unspecified.
std::optional<std::string_view> Capabilities() const noexcept;
private:
static std::string_view safe(const char* s) noexcept { return s ? s : ""; }
detail::Base<flModelInfo> handle_;
};
// ===========================================================================
// Content structs (pure data — no methods)
//
// All `*Content` structs returned by `Item::Get*()` are non-owning views over
// storage owned by the underlying `Item`. Pointers, `std::string_view`s, and
// nested `Item` parts remain valid only for the lifetime of the source `Item`
// (and until that Item is mutated). Copy fields into owning containers
// (`std::string`, `std::vector`, etc.) if you need them to outlive the Item.
// ===========================================================================
/// Content returned from a TEXT item. Carries the text plus a `flTextItemType` subtype tag so callers
/// can distinguish ordinary assistant text from reasoning / chain-of-thought content without losing data.
struct TextContent {
std::string_view text;
flTextItemType type = FOUNDRY_LOCAL_TEXT_ITEM_TYPE_DEFAULT;
};
/// Content returned from a BYTES item.
struct BytesContent {
flItemType item_type; ///< The type of item these bytes represent.
const void* data; ///< Raw byte buffer (item-internal, do not free).
size_t data_size; ///< Byte count.
};
/// Content returned from a TENSOR item. Data points into the item's internal buffer.
struct TensorContent {
flTensorDataType data_type;
const void* data;
std::vector<int64_t> shape;
};
/// Content returned from an IMAGE item. Either bytes (data + data_size + format) or URI.
struct ImageContent {
const void* data; ///< Raw bytes (nullptr when URI-based).
size_t data_size; ///< Byte count (0 when URI-based).
std::optional<std::string_view> format; ///< Image format.
std::optional<std::string_view> uri; ///< Set when image is provided as a URI.
};
/// Content returned from an AUDIO item. Either bytes (data + data_size + format) or URI.
struct AudioContent {
const void* data; ///< Raw bytes (nullptr when URI-based).
size_t data_size; ///< Byte count (0 when URI-based).
std::optional<std::string_view> format; ///< Audio format.
std::optional<std::string_view> uri; ///< Set when audio is provided as a URI.
int sample_rate; ///< Sample rate in Hz (0 = unspecified).
int channels; ///< Number of channels (0 = unspecified).
};
/// Content returned from a MESSAGE item.
///
/// `parts` exposes the typed content parts (TEXT / IMAGE / AUDIO `Item`s).
/// Each entry is a non-owning view over a part owned by the underlying message.
/// Use `Item::GetText()` / `GetImage()` / `GetAudio()` to read part data.
///
/// Most chat messages have a single TEXT part. Use `IsSimpleText()` to check
/// for that shape and `GetSimpleText()` to read its text without iterating.
struct MessageContent {
flMessageRole role;
std::vector<Item> parts;
std::optional<std::string_view> name;
/// True when the message has exactly one part and it is a TEXT item.
bool IsSimpleText() const;
/// Text of the single TEXT part. Throws if !IsSimpleText().
std::string GetSimpleText() const;
};
/// Content returned from a TOOL_CALL item.
struct ToolCallContent {
std::string_view call_id;
std::string_view name;
/// FUNCTION arguments are JSON. CUSTOM arguments are NUL-free UTF-8 text; a generated custom
/// payload containing an embedded NUL is invalid and is never truncated.
std::string_view arguments;
};
/// Content returned from a TOOL_RESULT item.
struct ToolResultContent {
std::string_view call_id;
std::string_view result;
};
/// One word in a SPEECH_SEGMENT. Optional fields use std::optional / empty string_view.
struct SpeechWord {
std::string_view text;
std::optional<int64_t> start_time_ms;
std::optional<int64_t> end_time_ms;
std::optional<float> confidence;
std::optional<std::string_view> speaker_id;
};
/// Content returned from a SPEECH_SEGMENT item (output-only).
///
/// See SpeechOutputTypes.md for the streaming model. PARTIAL `text` is the
/// cumulative current hypothesis for the segment, not a delta. As an entry of
/// a SpeechResultContent, `kind` is FINAL (or NONE for a single non-segmented
/// transcript).
struct SpeechSegmentContent {
flSpeechSegmentKind kind;
std::string_view text;
std::optional<int64_t> start_time_ms;
std::optional<int64_t> end_time_ms;
bool utterance_start;
std::vector<SpeechWord> words;
std::optional<std::string_view> language;
};
/// Content returned from a SPEECH_RESULT item (output-only).
/// `segments` exposes non-owning views over segment items owned by the result.
struct SpeechResultContent {
std::string_view text;
std::optional<std::string_view> language;
std::optional<int64_t> duration_ms;
std::vector<Item> segments;
};
// ===========================================================================
// Item
// ===========================================================================
/// Unified item wrapper. Supports both owning (mutable) and non-owning (read-only) modes.
class Item {
public:
/// Adopt an already-created item (owning, mutable).
explicit Item(flItem& raw);
/// Read-only view (non-owning).
explicit Item(const flItem& raw) : handle_(&raw) {}
Item(Item&&) noexcept = default;
Item& operator=(Item&&) noexcept = default;
// --- Read accessors (always work) ---
flItemType GetType() const noexcept;
BytesContent GetBytes() const;
TextContent GetText() const;
TensorContent GetTensor() const;
ImageContent GetImage() const;
AudioContent GetAudio() const;
MessageContent GetMessage() const;
ToolCallContent GetToolCall() const;
ToolResultContent GetToolResult() const;
SpeechSegmentContent GetSpeechSegment() const;
SpeechResultContent GetSpeechResult() const;
const flItem* native_handle() const noexcept { return handle_.get(); }
flItem* native_handle_mutable() { return handle_.get_mutable(); }
void detach() noexcept { handle_.detach(); }
// --- Static factory methods ---
/// Create a text item with an optional subtype tag (default = ordinary text).
static Item Text(const std::string& text,
flTextItemType type = FOUNDRY_LOCAL_TEXT_ITEM_TYPE_DEFAULT);
/// Create a non-owning bytes item. Caller must keep `data` alive for the item's lifetime.
static Item Bytes(flItemType item_type, const void* data, size_t data_size);
/// Create an owning bytes item. Ownership of `data` is transferred to the item.
static Item Bytes(flItemType item_type, void* data, size_t data_size,
std::function<void(const flBytesData*)> deleter);
/// Create a non-owning tensor item. Caller must keep `data` alive for the item's lifetime.
static Item Tensor(flTensorDataType data_type, const void* data, const int64_t* shape, size_t rank);
/// Create an owning tensor item. Ownership of `data` is transferred to the item.
static Item Tensor(flTensorDataType data_type, void* data, const int64_t* shape, size_t rank,
std::function<void(const flTensorData*)> deleter);
/// Create a non-owning image from raw bytes. format is e.g. "png". Caller must keep `data` alive.
static Item ImageFromData(const std::string& format, const void* data, size_t data_size);
/// Create an owning image from raw bytes. Ownership of `data` is transferred to the item.
static Item ImageFromData(const std::string& format, void* data, size_t data_size,
std::function<void(const flImageData*)> deleter);
/// Create an image from a URI (file path, URL, etc.).
static Item ImageFromUri(const std::string& uri, const std::optional<std::string>& format = std::nullopt);
/// Create a non-owning audio from raw bytes. format is e.g. "mp3". Caller must keep `data` alive.
static Item AudioFromData(const std::string& format, const void* data, size_t data_size,
int sample_rate = 0, int channels = 0);
/// Create an owning audio from raw bytes. Ownership of `data` is transferred to the item.
static Item AudioFromData(const std::string& format, void* data, size_t data_size,
std::function<void(const flAudioData*)> deleter,
int sample_rate = 0, int channels = 0);
/// Create an audio item from a URI (file path, URL, etc.).
static Item AudioFromUri(const std::string& uri, const std::optional<std::string>& format = std::nullopt,
int sample_rate = 0, int channels = 0);
/// Create a tool call item. Custom `arguments` must be NUL-free UTF-8 text; an embedded NUL is
/// invalid and is rejected rather than truncated.
static Item ToolCall(const std::string& call_id, const std::string& name, const std::string& arguments);
/// Create a tool result item.
static Item ToolResult(const std::string& call_id, const std::string& result);
protected:
// For ItemQueue to construct via item type
explicit Item(flItemType type);
detail::Base<flItem> handle_;
};
/// Describes a model's expected inputs and outputs.
struct InputOutputInfo {
std::vector<Item> inputs;
std::vector<Item> outputs;
};
/// A queue item containing an flItemQueue of sub-items.
/// Provides push/pop/iteration over contained items.
/// Used for streaming input data (eg. realtime audio) and output data (e.g. streaming tokens).
class ItemQueue : public Item {
public:
/// Construct with a new empty queue.
ItemQueue();
/// Push an item into the queue. Transfers ownership — do not use the Item after this call.
void Push(Item&& item);
/// Try to pop the front item. Returns nullopt if the queue is empty. Caller takes ownership.
std::optional<Item> TryPop();
/// Get the number of items currently in the queue.
size_t Size();
/// Mark the queue as finished (no more items will be pushed).
void MarkFinished();
/// Check whether the queue has been marked finished.
bool IsFinished();
};
/// A typed message item.
///
/// Convenience constructor over the C ABI's SetMessage call. Parts are passed
/// to the native layer, which deep-clones them; once the constructor returns
/// the caller's part vector can be safely dropped.
class MessageItem : public Item {
public:
/// Build a message with role, plain-text content, and optional participant name.
/// Wraps `content` in a single TEXT part.
MessageItem(flMessageRole role, const std::string& content,
const std::optional<std::string>& name = std::nullopt);
/// Build a message with role, typed content parts (TEXT / IMAGE / AUDIO),
/// and optional participant name. The native layer copies each part.
MessageItem(flMessageRole role, std::vector<Item> parts,
const std::optional<std::string>& name = std::nullopt);
};
// ===========================================================================
// Convenience message constructors — free functions returning Item
// ===========================================================================
/// Create a system message item.
inline MessageItem SystemMessage(const std::string& content,
const std::optional<std::string>& name = std::nullopt) {
return MessageItem(FOUNDRY_LOCAL_ROLE_SYSTEM, content, name);
}
/// Create a user message item.
inline MessageItem UserMessage(const std::string& content,
const std::optional<std::string>& name = std::nullopt) {
return MessageItem(FOUNDRY_LOCAL_ROLE_USER, content, name);
}
/// Create an assistant message item.
inline MessageItem AssistantMessage(const std::string& content,
const std::optional<std::string>& name = std::nullopt) {
return MessageItem(FOUNDRY_LOCAL_ROLE_ASSISTANT, content, name);
}
/// Create a developer message item.
inline MessageItem DeveloperMessage(const std::string& content,
const std::optional<std::string>& name = std::nullopt) {
return MessageItem(FOUNDRY_LOCAL_ROLE_DEVELOPER, content, name);
}
// ===========================================================================
// IModel interface
// ===========================================================================
/// IModel is an internal abstraction used by the SDK and by tests for dependency injection. It must NOT be
/// implemented by user code: every IModel reference accepted by the SDK is required at runtime to be a concrete
/// `Model` instance, and is downcast accordingly via `detail::AsModel`. Custom subclasses will trigger an
/// exception at the downcast site (FOUNDRY_LOCAL_ERROR_INVALID_ARGUMENT).
class IModel {
public:
virtual ~IModel() = default;
virtual ModelInfo GetInfo() const = 0;
virtual bool IsCached() const = 0;
virtual bool IsLoaded() const = 0;
virtual std::string_view GetPath() const = 0;
virtual InputOutputInfo GetInputOutputInfo() const = 0;
virtual ModelList GetVariants() const = 0;
virtual void SelectVariant(const IModel& variant) = 0;
virtual void Download(std::function<int(float)> progress = nullptr) = 0;
virtual void Load() = 0;
virtual void Unload() = 0;
virtual void RemoveFromCache() = 0;
/// Identity hook used by `detail::AsModel` to recover the concrete `Model` from an `IModel&` without
/// requiring RTTI / `dynamic_cast`. The default implementations return nullptr; the concrete `Model`
/// overrides to return `this`. Custom `IModel` subclasses must not override these — doing so would
/// defeat the safety check at the downcast site.
virtual Model* AsConcreteModelHook() noexcept { return nullptr; }
virtual const Model* AsConcreteModelHook() const noexcept { return nullptr; }
};
// ===========================================================================
// 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*).
explicit Model(flModel& m) : handle_(&m) {}
/// Const construction (for non-owning read-only views within ModelList).
explicit Model(const flModel& m) : handle_(&m) {}
Model(Model&&) noexcept = default;
Model& operator=(Model&&) noexcept = default;
// IModel overrides
ModelInfo GetInfo() const override;
bool IsCached() const override;
bool IsLoaded() const override;
std::string_view GetPath() const override;
InputOutputInfo GetInputOutputInfo() const override;
ModelList GetVariants() const override;
void SelectVariant(const IModel& variant) override;
void Download(std::function<int(float)> progress = nullptr) override;
void Load() override;
void Unload() override;
void RemoveFromCache() override;
// Identity hooks — see IModel::AsConcreteModelHook.
Model* AsConcreteModelHook() noexcept override { return this; }
const Model* AsConcreteModelHook() const noexcept override { return this; }
/// Native handle accessors (not on IModel — keeps interface clean for mocks).
const flModel* native_handle() const noexcept { return handle_.get(); }
flModel* native_handle_mutable() { return handle_.get_mutable(); }
private:
detail::Base<flModel> handle_;
};
// ===========================================================================
// 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);
ModelList(ModelList&&) noexcept = default;
ModelList& operator=(ModelList&&) noexcept = default;
ModelList(const ModelList&) = delete;
ModelList& operator=(const ModelList&) = delete;
size_t size() const noexcept;
bool empty() const noexcept { return models_.empty(); }
const std::unique_ptr<IModel>& operator[](size_t index) const noexcept { return models_[index]; }
const std::unique_ptr<IModel>& at(size_t index) const { return models_.at(index); }
const std::unique_ptr<IModel>& front() const noexcept { return models_.front(); }
auto begin() const noexcept { return models_.begin(); }
auto end() const noexcept { return models_.end(); }
private:
detail::Base<flModelList> handle_;
std::vector<std::unique_ptr<IModel>> models_;
};
// ===========================================================================
// ICatalog interface
// ===========================================================================
class ICatalog {
public:
virtual ~ICatalog() = default;
virtual std::string_view GetName() const = 0;
virtual ModelList GetModels() const = 0;
/// Get every individual model variant currently present in the local cache.
virtual ModelList GetCachedModels() const = 0;
/// Get every loaded leaf variant, independently of each alias group's selected variant.
virtual ModelList GetLoadedModels() const = 0;
virtual std::unique_ptr<IModel> GetModel(const std::string& alias) const = 0;
virtual std::unique_ptr<IModel> GetModelVariant(const std::string& model_id) const = 0;
virtual std::unique_ptr<IModel> GetLatestVersion(const IModel& model) const = 0;
/// Get all versions of a model alias. `model_alias` must be non-empty.
/// `variant_name` optionally narrows the result to a single variant; empty
/// 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 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>` and `metadata.Task()` is required.
/// Metadata is copied. Missing display name and modalities receive defaults, while context length comes from
/// model.context_length, then search.max_length, when positive. A caller-supplied provider remains the default
/// IModel::Load override; when omitted, runtime metadata remains unset and genai_config.json selects the provider.
/// The catalog does not take ownership of `model_path` and never deletes its contents.
virtual std::unique_ptr<IModel> RegisterModel(const std::string& model_path, const std::string& model_id,
const ModelInfo& metadata) = 0;
/// Unregister without deleting model assets. A model ID removes only that version/variant; an alias removes all of
/// its registered versions and variants. 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& alias_or_model_id) = 0;
};
// ===========================================================================
// Catalog — concrete ICatalog implementation using composition
// ===========================================================================
class Catalog final : public ICatalog {
public:
/// 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) {}
Catalog(Catalog&&) noexcept = default;
Catalog& operator=(Catalog&&) noexcept = default;
std::string_view GetName() const override;
ModelList GetModels() const override;
ModelList GetCachedModels() const override;
ModelList GetLoadedModels() const override;
std::unique_ptr<IModel> GetModel(const std::string& alias) const override;
std::unique_ptr<IModel> GetModelVariant(const std::string& model_id) const override;
std::unique_ptr<IModel> GetLatestVersion(const IModel& model) const override;
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_;
};
// ===========================================================================
// Manager
// ===========================================================================
/// @brief Main entry point for the Foundry Local SDK. Manages configuration, catalogs, and the embedded web service.
///
/// Construct a single Manager at application startup, then use it to access catalogs and manage the web service.
/// Manager is thread-safe for concurrent use from multiple threads.
/// Shutdown() can be called from any thread.
/// Manager is designed to be long-lived and only one instance is allowed at any point in time.
/// The Manager can be destroyed and recreated if you need to change configuration.
class Manager {
public:
explicit Manager(Configuration&& config);
~Manager() = default;
Manager(Manager&&) noexcept = default;
Manager& operator=(Manager&&) noexcept = default;
Manager(const Manager&) = delete;
Manager& operator=(const Manager&) = delete;
const Configuration& GetConfiguration() const { return config_; }
/// Get a manager-owned catalog for querying models.
ICatalog& GetCatalog(flCatalogType type = FOUNDRY_LOCAL_CATALOG_PUBLIC) const;
/// Start the embedded web service.
void StartWebService();
/// Stop the embedded web service.
void StopWebService();
/// Get the URLs the web service is bound to. Returns an empty vector when the web service is not
/// running (i.e. before StartWebService() or after StopWebService()); callers can use that as an
/// "is running" check without a separate API.
std::vector<std::string> GetWebServiceEndpoints() const;
/// Get discoverable execution providers and their registration status.
std::vector<EpInfo> GetDiscoverableEps() const;
/// Download and register execution providers. Blocking.
/// @param ep_names EP names to download. Empty = all discoverable EPs.
/// @param progress Optional callback: (ep_name, percent) -> return true to continue, false to cancel.
void DownloadAndRegisterEps(
const std::vector<std::string>& ep_names = {},
std::function<bool(std::string_view ep_name, float percent)> progress = nullptr);
/// Whether an EP download/registration operation is currently in progress.
bool IsEpDownloadInProgress() const;
/// Begin graceful shutdown. Safe to call from any thread. Idempotent.
void Shutdown();
/// Check if Shutdown() has been called.
bool IsShutdownRequested() const;
private:
struct CatalogCollection {
explicit CatalogCollection(const flManager* manager);
mutable Catalog public_;
mutable Catalog local_;
};
detail::Base<flManager> handle_;
Configuration config_;
CatalogCollection catalogs_;
};
// ===========================================================================
// ToolDefinition
// ===========================================================================
/// C++ wrapper for flToolDefinition. Sets the version field automatically.
struct ToolDefinition {
std::string name; ///< Tool name. Must be non-empty, case-sensitive, and unique across kinds.
std::string description; ///< Tool description for model context.
/// JSON schema defining the tool's arguments. Required for a function tool; must stay empty for a
/// custom tool, whose schema is synthesized.
std::string json_schema;
/// Tool kind. Determines how a generated call's arguments are shaped: JSON for a function tool,
/// raw text for a custom tool.
flToolKind kind = FOUNDRY_LOCAL_TOOL_KIND_FUNCTION;
/// Function tool: `json_schema` is required and must be valid JSON.
ToolDefinition(std::string name, std::string description, std::string json_schema)
: name(std::move(name)), description(std::move(description)), json_schema(std::move(json_schema)) {}
/// Custom tool: the model is prompted with a synthesized single-string schema. Generated
/// arguments are delivered as NUL-free UTF-8 text; an embedded NUL is invalid and is never
/// truncated.
static ToolDefinition Custom(std::string name, std::string description) {
ToolDefinition definition(std::move(name), std::move(description), std::string{});
definition.kind = FOUNDRY_LOCAL_TOOL_KIND_CUSTOM;
return definition;
}
/// Convert to the C struct for passing across the ABI boundary.
flToolDefinition ToC() const {
static_assert(FOUNDRY_LOCAL_TOOL_DEFINITION_VERSION == 2,
"flToolDefinition may have new fields; initialize them here before updating this assertion");
if (name.find('\0') != std::string::npos || description.find('\0') != std::string::npos ||
json_schema.find('\0') != std::string::npos) {
throw Error("Tool definition fields must not contain embedded NUL characters",
FOUNDRY_LOCAL_ERROR_INVALID_ARGUMENT);
}
// Zero-initialized and assigned by name rather than aggregate-initialized, so a field appended
// to flToolDefinition in a future version defaults to zero (the value that preserves the older
// behavior) instead of being left uninitialized.
flToolDefinition c_def{};
c_def.version = FOUNDRY_LOCAL_TOOL_DEFINITION_VERSION;
c_def.name = name.c_str();
c_def.description = description.c_str();
c_def.json_schema = json_schema.c_str();
c_def.kind = kind;
return c_def;
}
};
// ===========================================================================
// RequestOptions — typed options for inference requests
// ===========================================================================
/// Pure sampling / decoder knobs. Each field is std::optional — only set values are forwarded to the C ABI.
struct SearchOptions {
std::optional<float> temperature; ///< Sampling temperature [0.0, 2.0].
std::optional<float> top_p; ///< Nucleus sampling [0.0, 1.0].