Add SDK v2 telemetry core#879
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Adds SDK v2 telemetry infrastructure, 1DS transport, correlation contexts, and instrumentation across inference, downloads, catalogs, EP registration, and HTTP services.
Changes:
- Introduces typed telemetry payloads, trackers, metadata, and optional 1DS upload support.
- Instruments SDK operations and streaming HTTP routes with correlated telemetry.
- Adds telemetry dependencies, build options, tests, and linker-size optimizations.
Reviewed changes
Copilot reviewed 49 out of 49 changed files in this pull request and generated 20 comments.
Show a summary per file
| File | Description |
|---|---|
sdk_v2/cpp/vcpkg.json |
Adds telemetry dependencies and feature. |
sdk_v2/cpp/test/internal_api/web_service_test.cc |
Tests HTTP telemetry behavior. |
sdk_v2/cpp/test/internal_api/telemetry_test.cc |
Updates telemetry unit tests. |
sdk_v2/cpp/test/internal_api/null_telemetry.h |
Updates no-op test sink. |
sdk_v2/cpp/src/telemetry/telemetry.h |
Defines telemetry API and payloads. |
sdk_v2/cpp/src/telemetry/telemetry.cc |
Maps actions and statuses. |
sdk_v2/cpp/src/telemetry/telemetry_metadata.h |
Declares process metadata. |
sdk_v2/cpp/src/telemetry/telemetry_metadata.cc |
Collects platform metadata. |
sdk_v2/cpp/src/telemetry/telemetry_logger.h |
Expands local telemetry logger. |
sdk_v2/cpp/src/telemetry/telemetry_logger.cc |
Formats typed telemetry events. |
sdk_v2/cpp/src/telemetry/telemetry_environment.h |
Declares runtime telemetry gating. |
sdk_v2/cpp/src/telemetry/telemetry_environment.cc |
Implements CI/test detection. |
sdk_v2/cpp/src/telemetry/telemetry_action_tracker.h |
Adds invocation-context tracking. |
sdk_v2/cpp/src/telemetry/telemetry_action_tracker.cc |
Emits correlated action events. |
sdk_v2/cpp/src/telemetry/one_ds_tenant_token.h.in |
Generates the tenant-token header. |
sdk_v2/cpp/src/telemetry/one_ds_telemetry.h |
Declares the 1DS sink. |
sdk_v2/cpp/src/telemetry/one_ds_telemetry.cc |
Implements 1DS event uploads. |
sdk_v2/cpp/src/telemetry/invocation_context.h |
Defines correlation context. |
sdk_v2/cpp/src/telemetry/invocation_context.cc |
Generates correlation UUIDs. |
sdk_v2/cpp/src/telemetry/ep_download_tracker.h |
Declares EP telemetry tracker. |
sdk_v2/cpp/src/telemetry/ep_download_tracker.cc |
Emits EP attempt details. |
sdk_v2/cpp/src/telemetry/download_tracker.h |
Declares model-download tracker. |
sdk_v2/cpp/src/telemetry/download_tracker.cc |
Emits model-download telemetry. |
sdk_v2/cpp/src/service/web_service.cc |
Instruments status and unmatched routes. |
sdk_v2/cpp/src/service/responses_handler.h |
Passes streaming route trackers. |
sdk_v2/cpp/src/service/responses_handler.cc |
Instruments Responses API operations. |
sdk_v2/cpp/src/service/models_handlers.cc |
Instruments model endpoints. |
sdk_v2/cpp/src/service/handler_utils.h |
Extracts request user agents. |
sdk_v2/cpp/src/service/embeddings_handler.cc |
Instruments embedding inference. |
sdk_v2/cpp/src/service/chat_completions_handler.h |
Extends streaming tracker ownership. |
sdk_v2/cpp/src/service/chat_completions_handler.cc |
Instruments chat streaming. |
sdk_v2/cpp/src/service/audio_transcriptions_handler.h |
Extends audio tracker ownership. |
sdk_v2/cpp/src/service/audio_transcriptions_handler.cc |
Instruments audio streaming. |
sdk_v2/cpp/src/manager.h |
Adjusts telemetry lifetime ordering. |
sdk_v2/cpp/src/manager.cc |
Constructs sinks and manages sessions. |
sdk_v2/cpp/src/inferencing/session/session.h |
Adds per-request telemetry context. |
sdk_v2/cpp/src/inferencing/session/session.cc |
Emits inference usage metrics. |
sdk_v2/cpp/src/ep_detection/ep_detector.h |
Accepts an optional telemetry sink. |
sdk_v2/cpp/src/ep_detection/ep_detector.cc |
Instruments EP registration. |
sdk_v2/cpp/src/download/download_manager.h |
Extends download telemetry API. |
sdk_v2/cpp/src/download/download_manager.cc |
Instruments model downloads. |
sdk_v2/cpp/src/download/blob_downloader.h |
Defines download statistics. |
sdk_v2/cpp/src/download/blob_downloader.cc |
Collects transfer statistics. |
sdk_v2/cpp/src/catalog/catalog_client.h |
Extends catalog telemetry API. |
sdk_v2/cpp/src/catalog/catalog_client.cc |
Emits catalog-fetch events. |
sdk_v2/cpp/src/catalog/azure_model_catalog.h |
Stores the telemetry sink. |
sdk_v2/cpp/src/catalog/azure_model_catalog.cc |
Adds catalog URL dimensions. |
sdk_v2/cpp/CMakeLists.txt |
Configures 1DS and linker stripping. |
sdk_v2/cpp/build.py |
Adds telemetry build options. |
Comments suppressed due to low confidence (1)
sdk_v2/cpp/src/service/responses_handler.cc:605
- Calling
Remove()makes shutdown stop waiting for this worker, but the capturedroute_trackeris destroyed only after the lambda returns. Manager teardown can consequently destroy telemetry in that gap and the tracker destructor will dereference freed telemetry. Emit/destroy the tracker before untracking the thread.
// Terminal event per spec
body_ptr->Push("data: [DONE]\n\n");
body_ptr->Finish();
tracker.Remove(std::this_thread::get_id());
| if args.telemetry_token is not None: | ||
| command += [f"-DFOUNDRY_LOCAL_TELEMETRY_TOKEN={args.telemetry_token}"] |
| // GetVersionExA is deprecated and lies for unmanifested apps. The reliable | ||
| // approach is to read the build number directly from kernel32 via | ||
| // RtlGetVersion, or fall back to the OS version registry. For now, use | ||
| // GetVersionEx — the deprecation only affects apps without a manifest, and | ||
| // Foundry Local has a manifest declaring Win10 / Win11 compat. |
| // Re-throw to preserve existing semantics — the wrapper RAII guard above | ||
| // resets download_in_progress_; the tracker dtor records the EP event. | ||
| throw; |
| if (tracker) { | ||
| tracker->RecordDownloadComplete(ActionStatus::kSuccess, "Installed"); | ||
| tracker->RecordRegisterComplete(ActionStatus::kSuccess, "Registered"); |
| std::unique_ptr<DownloadTracker> tracker; | ||
| if (telemetry_ != nullptr) { | ||
| tracker = std::make_unique<DownloadTracker>(info.model_id, user_agent, *telemetry_); | ||
| tracker->SetLockWaitMs(lock_wait_ms); | ||
| tracker->SetMaxConcurrency(static_cast<int32_t>(max_concurrency_)); | ||
| } |
| body_ptr->Finish(); | ||
| tracker.Remove(std::this_thread::get_id()); | ||
| thread_tracker.Remove(std::this_thread::get_id()); |
| telemetry_.RecordAction(action_, status_, context_, duration_ms); | ||
|
|
||
| if (!model_id_.empty()) { | ||
| telemetry_.RecordModelId(action_, model_id_); | ||
| telemetry_.RecordModelId(action_, model_id_, status_, context_); |
| DownloadTracker::~DownloadTracker() { | ||
| // Emit the Download event regardless of outcome. The default status is | ||
| // kFailure so abrupt exits (exceptions) are recorded as failures. | ||
| telemetry_.RecordDownload(info_); | ||
| } |
| EpDownloadTracker::~EpDownloadTracker() { | ||
| // Mirror neutron-server: if the caller didn't reach Done() or | ||
| // RecordRegisterComplete, assume the abrupt exit was an exception path and | ||
| // record any unfinished stage as kFailure. | ||
| RecordEvent(ActionStatus::kFailure); | ||
| } |
| parser.add_argument( | ||
| "--no_telemetry", action="store_true", | ||
| help="Skip building the 1DS (cpp-client-telemetry) bridge. Useful while the vcpkg " | ||
| "port (microsoft/vcpkg#52316) is unmerged or when building forks that should " | ||
| "not link any telemetry transport. Local diagnostic logging via TelemetryLogger " | ||
| "still works.", | ||
| ) |
Bring PR #879's Copilot round-1 telemetry fixes into the stacked hardening PR while preserving the hardening branch's broader implementations for conflicting files. Files changed: - sdk_v2/cpp/src/download/download_manager.cc - sdk_v2/cpp/src/ep_detection/ep_detector.cc - sdk_v2/cpp/src/inferencing/session/session.cc - sdk_v2/cpp/src/telemetry/download_tracker.h - sdk_v2/cpp/src/telemetry/telemetry_metadata.cc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 297bc539-5f7f-4a70-9411-48e91a5bf532
| .count(); | ||
| usage.total_tokens = static_cast<int32_t>(response.usage.total_tokens); | ||
| usage.input_token_count = static_cast<int32_t>(response.usage.prompt_tokens); | ||
| telemetry_.RecordModelUsage(usage); |
| info.duration_ms = duration_ms; | ||
| info.model_count = model_count; | ||
| info.error_message = error; | ||
| telemetry->RecordCatalogFetch(info); |
| attempt_info.duration_ms = std::chrono::duration_cast<std::chrono::milliseconds>( | ||
| std::chrono::steady_clock::now() - attempt_start) | ||
| .count(); | ||
| telemetry_->RecordEpDownloadAttempt(attempt_info); |
| web_service_running_ = true; | ||
| // Open an app-usage session for the lifetime of the running service so events | ||
| // carry ext.app.sesId and the backend gets session duration. | ||
| telemetry_->StartSession(); |
| web_service_.reset(); | ||
| web_service_running_ = false; | ||
| bound_urls_.clear(); | ||
| telemetry_->EndSession(); |
| std::string correlation_id; | ||
| bool stream = false; // True if the inference was streamed (SSE) vs a single response | ||
| bool indirect = false; // True if the inference was driven by another action (e.g. an HTTP route) | ||
| int64_t time_to_first_token_ms = 0; |
| int64_t lock_wait_ms = std::chrono::duration_cast<std::chrono::milliseconds>( | ||
| clock::now() - lock_wait_start) | ||
| .count(); |
| if (auto slash = rest.find('/'); slash == std::string::npos) { | ||
| out.endpoint = rest; | ||
| } else { | ||
| out.endpoint = rest.substr(0, slash); | ||
| path = rest.substr(slash + 1); |
| ActionTracker create_tracker(Action::kSessionCreate, ctx_.telemetry, session_ctx); | ||
| create_tracker.SetModelId(model_name); | ||
| ChatSession session(*model, *loaded, ctx_.logger, ctx_.telemetry); | ||
| create_tracker.SetStatus(ActionStatus::kSuccess); |
| ActionTracker create_tracker(Action::kSessionCreate, ctx_.telemetry, session_ctx); | ||
| create_tracker.SetModelId(model_name); | ||
| session = std::make_unique<ChatSession>(*model, *loaded, ctx_.logger, ctx_.telemetry); | ||
| create_tracker.SetStatus(ActionStatus::kSuccess); |
| ActionTracker create_tracker(Action::kSessionCreate, ctx_.telemetry, session_ctx); | ||
| create_tracker.SetModelId(model_name); | ||
| EmbeddingsSession session(*model, *loaded, ctx_.logger, ctx_.telemetry); | ||
| create_tracker.SetStatus(ActionStatus::kSuccess); |
| ActionTracker create_tracker(Action::kSessionCreate, ctx_.telemetry, session_ctx); | ||
| create_tracker.SetModelId(model_name); | ||
| AudioSession session(*model, *loaded, ctx_.logger, ctx_.telemetry); | ||
| create_tracker.SetStatus(ActionStatus::kSuccess); |
| // Use the context the caller staged (an HTTP route stages an indirect child | ||
| // with the route's correlation id); otherwise mint a direct context per call | ||
| // for direct SDK use. | ||
| InvocationContext context = request_context_ ? *request_context_ : InvocationContext::Direct(); | ||
| context.EnsureCorrelationId(); |
| /// Payload for the CatalogFetch event — emitted once per access to a model | ||
| /// catalog source (the live Azure catalog or the embedded static snapshot). | ||
| struct CatalogFetchInfo { |
| // RAII telemetry tracker — emits a "Download" event on destruction with whatever | ||
| // fields have been populated. Default status is kFailure so abrupt exits (exceptions) | ||
| // are recorded as failures; the happy path explicitly sets kSuccess / kSkipped. | ||
| std::unique_ptr<DownloadTracker> tracker; | ||
| if (telemetry_ != nullptr) { |
| bool TelemetryEnvironment::IsTruthyValue(std::string_view value) { | ||
| auto trimmed = Trim(value); | ||
| if (trimmed.empty()) { | ||
| return false; | ||
| } |
| // Use the W variant so we don't depend on the legacy CRT _CRT_SECURE_NO_WARNINGS. | ||
| // Env-var values are ASCII for the CI flags we care about; if a value is unicode | ||
| // we still get the bytes round-tripped correctly because we only do truthiness checks. |
Bring PR #879's second Copilot telemetry fixes into the hardening stack while retaining stronger redaction and lifecycle behavior already present here. Files changed: - sdk_v2/cpp/src/catalog/* - sdk_v2/cpp/src/download/download_manager.cc - sdk_v2/cpp/src/ep_detection/ep_detector.cc - sdk_v2/cpp/src/inferencing/session/session.cc - sdk_v2/cpp/src/service/web_service.cc - sdk_v2/cpp/src/telemetry/one_ds_telemetry.cc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 297bc539-5f7f-4a70-9411-48e91a5bf532
Bring PR #879's request-context, session-create timing, and telemetry contract fixes into the stacked hardening branch while preserving #880's session construction implementations. Files changed: - sdk_v2/cpp/src/download/download_manager.cc - sdk_v2/cpp/src/inferencing/session/session.* - sdk_v2/cpp/src/telemetry/telemetry.h Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 297bc539-5f7f-4a70-9411-48e91a5bf532
| // memory are not surfaced by the generators yet and stay at their unset values. | ||
| ModelUsageInfo usage; | ||
| usage.model_id = CatalogModel().Id(); | ||
| usage.execution_provider = CatalogModel().Info().execution_provider; |
| usage.total_tokens = static_cast<int32_t>(response.usage.total_tokens); | ||
| usage.input_token_count = static_cast<int32_t>(response.usage.prompt_tokens); |
| } | ||
| } | ||
|
|
||
| ++telemetry_num_providers; |
| auto model_infos = FetchAllModelInfosWithCachedModels(*client, cached_model_ids, logger_, | ||
| telemetry_, &base_info); |
| bound_urls_ = web_service_->Start(endpoints); | ||
| web_service_running_ = true; | ||
| // Open an app-usage session for the lifetime of the running service so events | ||
| // carry ext.app.sesId and the backend gets session duration. | ||
| try { | ||
| telemetry_->StartSession(); |
| bool TelemetryEnvironment::IsTruthyValue(std::string_view value) { | ||
| auto trimmed = Trim(value); |
| // Use the W variant so we don't depend on the legacy CRT _CRT_SECURE_NO_WARNINGS. | ||
| // Env-var values are ASCII for the CI flags we care about; if a value is unicode | ||
| // we still get the bytes round-tripped correctly because we only do truthiness checks. |
| "nlohmann-json", | ||
| "spdlog" | ||
| "spdlog", | ||
| { "name": "sqlite3", "default-features": false } |
| } | ||
| } | ||
|
|
||
| ++telemetry_num_providers; |
| auto response = HandleNonStreaming(session_ref, session_request); | ||
| tracker->SetStatus(ActionStatus::kSuccess); | ||
| return response; |
| auto response = HandleNonStreaming(session_ref, session_request); | ||
| tracker->SetStatus(ActionStatus::kSuccess); | ||
| return response; |
| # Strip unreferenced symbols and fold identical COMDATs in Release builds. | ||
| # MSVC's /DEBUG (needed for PDBs) silently disables /OPT:REF and /OPT:ICF | ||
| # unless they are explicitly re-enabled. /INCREMENTAL:NO is required because | ||
| # incremental linking also prevents dead-code elimination. | ||
| target_link_options(foundry_local PRIVATE |
| std::random_device rd; | ||
| std::mt19937_64 gen{(static_cast<uint64_t>(rd()) << 32) | rd()}; | ||
| uint64_t hi = gen(); | ||
| uint64_t lo = gen(); |
| auto token = value.substr(i, token_end - i); | ||
| if (token.find("://") != std::string_view::npos) { | ||
| out += "[url]"; | ||
| } else { |
| // Per-inference Model event — emitted on success with whatever metrics this run | ||
| // produced, sharing the action's correlation id and indirect flag. TTFT and | ||
| // memory are not surfaced by the generators yet and stay at their unset values. | ||
| ModelUsageInfo usage; |
Resolve the latest Copilot review comments on PR #879. EP telemetry: use documented EPDownload action names and report requested provider count independent of cancellation or unknown-name filtering. Model telemetry: report the resolved session execution provider and count actual chat messages, including OpenAI JSON message arrays. Route telemetry: derive non-streaming chat/audio status from the HTTP response instead of unconditionally marking success. Privacy and packaging: redact free-form uploaded 1DS error text, make correlation ID seeding resilient to random_device failure, keep sqlite3 under the telemetry vcpkg feature, and move linker stripping out of the core PR. Files changed: - sdk_v2/cpp/CMakeLists.txt - sdk_v2/cpp/vcpkg.json - sdk_v2/cpp/src/ep_detection/ep_detector.cc - sdk_v2/cpp/src/inferencing/session/session.cc - sdk_v2/cpp/src/inferencing/generative/{chat,audio,embeddings}/*session* - sdk_v2/cpp/src/service/{chat_completions,audio_transcriptions}_handler.cc - sdk_v2/cpp/src/service/handler_utils.h - sdk_v2/cpp/src/telemetry/{invocation_context,one_ds_telemetry,telemetry}.cc - sdk_v2/cpp/test/internal_api/{ep_detector,telemetry}_test.cc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 297bc539-5f7f-4a70-9411-48e91a5bf532
| ++telemetry_failed; | ||
| result.failed_eps.push_back(bs->Name()); | ||
| result.success = false; | ||
| if (tracker) { | ||
| // The bootstrapper conflated download + register and returned false. | ||
| // Record the combined operation as the register phase rather than fabricating a download/register split. | ||
| tracker->RecordDownloadComplete(ActionStatus::kSkipped, | ||
| was_registered_before ? "Registered" : "NotPresent"); | ||
| tracker->RecordRegisterComplete(ActionStatus::kFailure, |
| TestHttpGet(urls[0] + "/status"); | ||
| TestHttpGet(urls[0] + "/status"); | ||
| TestHttpGet(urls[0] + "/status"); |
Bring PR #879 round-5 Copilot fixes into the stacked hardening branch while preserving stack-only packaging hardening. Keep #880's stricter unknown-EP behavior and include unmatched requested EPs in aggregate telemetry failure counts. Files changed: - sdk_v2/cpp/vcpkg.json - sdk_v2/cpp/src/ep_detection/ep_detector.cc - sdk_v2/cpp/src/inferencing/session/session.cc - sdk_v2/cpp/src/inferencing/generative/{chat,audio,embeddings}/*session* - sdk_v2/cpp/src/service/{chat_completions,audio_transcriptions}_handler.cc - sdk_v2/cpp/src/service/handler_utils.h - sdk_v2/cpp/src/telemetry/{invocation_context,one_ds_telemetry,telemetry}.cc - sdk_v2/cpp/test/internal_api/{ep_detector,telemetry}_test.cc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 297bc539-5f7f-4a70-9411-48e91a5bf532
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 83 out of 83 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
sdk_v2/cpp/src/manager.cc:354
disable_telemetry=truestill installs a telemetry sink:OneDsTelemetrymirrors every event toTelemetryLogger, and the no-1DS branch always usesTelemetryLoggerdirectly. Consequently model IDs, user agents, timings, and errors continue to be collected in local logs even though every public configuration contract says the SDK “collects and uploads nothing.” Select a no-opITelemetryimplementation when the option is set (before passing it to the detector, catalog, downloads, and sessions); keep local mirroring only for CI/transport-unavailable cases where telemetry was not explicitly disabled.
#if FOUNDRY_LOCAL_HAS_1DS
// OneDsTelemetry keeps an always-on local diagnostic mirror and uploads to 1DS only when telemetry is
// not disabled by configuration and not in a CI environment.
telemetry_ = std::make_unique<OneDsTelemetry>(config_.app_name, *logger_, config_.disable_telemetry);
#else
telemetry_ = std::make_unique<TelemetryLogger>(config_.app_name, *logger_);
#endif
| std::string out(static_cast<size_t>(needed - 1), '\0'); | ||
| const int written = ::WideCharToMultiByte(CP_UTF8, 0, wide_value, -1, out.data(), needed, nullptr, nullptr); | ||
| return written > 0 ? TrimVersionString(std::move(out)) : std::string{}; |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 83 out of 83 changed files in this pull request and generated 5 comments.
Comments suppressed due to low confidence (1)
sdk_v2/cpp/src/manager.cc:353
disable_telemetrydoes not implement the documented “collects and uploads nothing” contract.OneDsTelemetrystill sends every event to itsTelemetryLoggermirror when disabled, while telemetry-off builds unconditionally selectTelemetryLoggerand ignore the flag entirely. Select a true no-op sink before wiring telemetry into the subsystems, and verify that disabling telemetry emits neither event payloads nor telemetry log records.
#if FOUNDRY_LOCAL_HAS_1DS
// OneDsTelemetry keeps an always-on local diagnostic mirror and uploads to 1DS only when telemetry is
// not disabled by configuration and not in a CI environment.
telemetry_ = std::make_unique<OneDsTelemetry>(config_.app_name, *logger_, config_.disable_telemetry);
#else
telemetry_ = std::make_unique<TelemetryLogger>(config_.app_name, *logger_);
| /// Optional. Disable all telemetry. When true, the SDK collects and uploads no telemetry (no 1DS | ||
| /// uploader is created and no device identifier is written). Defaults to false (telemetry enabled). | ||
| FL_API_STATUS(SetDisableTelemetry, _In_ flConfiguration* config, bool disable); |
| void EpDownloadTracker::Done() { | ||
| RecordEvent(ActionStatus::kSkipped); | ||
| } |
| - **Environment / device:** Foundry Local version, operating system, architecture, CPU details, and total memory, plus a device identifier. On Windows, Linux, and macOS the device identifier is a locally generated random UUID (**not** a hardware identifier such as a machine GUID); only a hashed form is transmitted, and it can be reset. On Android, Foundry Local uses the device identifier provided by the 1DS SDK instead of creating a separate Foundry-generated device id. | ||
| - **Usage:** model and execution-provider activity, model and execution-provider download activity, and request outcomes. | ||
| - **Errors:** error information for reliability analysis. Error text is scrubbed of filesystem paths, URLs, and other free-form content before transmission. |
| if(WIN32) | ||
| target_link_libraries(${TARGET} ${LINK_SCOPE} dbghelp bcrypt) | ||
| target_link_libraries(${TARGET} ${LINK_SCOPE} dbghelp bcrypt version) | ||
| # UWP builds have CMAKE_SYSTEM_NAME = "WindowsStore"; desktop = "Windows". | ||
| # WinHTTP is not available in UWP, so only define this on desktop Windows. | ||
| if(NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore") |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 83 out of 83 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
sdk_v2/cpp/CMakeLists.txt:241
- These telemetry sources are compiled for every
WIN32target even though only the 1DS dependency is disabled forWindowsStore.device_id.ccandtelemetry_metadata.ccstill compile desktop-only APIs such asRegCreateKeyExA,GetFileVersionInfoA, andGlobalMemoryStatusEx, so UWP builds can fail or fail app certification. Gate/provide UWP implementations for the core telemetry sources as well as disabling 1DS.
src/service/audio_transcriptions_handler.cc
src/service/embeddings_handler.cc
src/service/chat_completions_handler.cc
src/service/models_handlers.cc
src/service/responses_handler.cc
src/service/web_service.cc
src/telemetry/telemetry.cc
src/telemetry/telemetry_action_tracker.cc
src/telemetry/device_id.cc
sdk_v2/cpp/src/ep_detection/ep_detector.cc:269
- On every successful combined
DownloadAndRegistercall this reports the download phase asSkipped, even when the bootstrapper actually downloaded a package. That makesDownloadStatus/DownloadTimeMsinaccurate for the main success path. The bootstrapper result needs to expose whether download occurred (and its timing/status), or this event should report only an aggregate phase that can be measured correctly.
tracker->RecordDownloadComplete(ActionStatus::kSkipped,
was_registered_before ? "Registered" : "Unknown");
tracker->RecordRegisterComplete(ActionStatus::kSuccess, "Registered");
| CatalogFetchInfo base_info; | ||
| base_info.endpoint = parsed.endpoint; | ||
| base_info.region = parsed.region; | ||
| base_info.format = parsed.format; |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 83 out of 83 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
sdk_v2/cpp/src/telemetry/telemetry_metadata.cc:85
neededincludes the terminating NUL because the input length is-1, butoutreserves onlyneeded - 1bytes while the conversion is allowed to writeneeded. On Windows this writes one byte past the string buffer whenever a host version is found. Allocate space for the terminator, then remove it after conversion.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 83 out of 83 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (4)
sdk_v2/cpp/src/telemetry/telemetry_metadata.cc:84
neededincludes the terminating NUL, butoutreserves onlyneeded - 1bytes whileWideCharToMultiByteis told it can writeneeded. On Windows this writes one byte past the string's size whenever an EXE version string is found. Allocateneededbytes;TrimVersionStringwill remove the trailing NUL.
sdk_v2/cpp/docs/Privacy.md:17- The “What is collected” disclosure is incomplete relative to the upload implementation:
ProcessInfosends the process name and locale, common context sends the configured app name, action events send the HTTP User-Agent, and audio events can send the request language. Please disclose these fields (or stop uploading them) so this privacy document accurately describes the telemetry payload.
- **Environment / device:** Foundry Local version, operating system, architecture, CPU details, and total memory, plus a device identifier. On Windows, Linux, and macOS the device identifier is a locally generated random UUID (**not** a hardware identifier such as a machine GUID); only a hashed form is transmitted, and it can be reset. On Android, Foundry Local uses the device identifier provided by the 1DS SDK instead of creating a separate Foundry-generated device id.
sdk_v2/cpp/CMakeLists.txt:311
- The newly compiled Windows device-ID implementation directly calls
OpenProcessToken,GetTokenInformation,RegGetValueA, andRegCreateKeyExA, all of which requireAdvapi32.lib. It is compiled even whenFOUNDRY_LOCAL_USE_TELEMETRY=OFF, so a no-telemetry Windows build cannot rely on the 1DS target to supply that library and will fail with unresolved externals.
target_link_libraries(${TARGET} ${LINK_SCOPE} dbghelp bcrypt version)
sdk_v2/cpp/CMakeLists.txt:52
- Turning off the 1DS transport is not sufficient to keep UWP buildable:
device_id.ccandtelemetry_metadata.ccare still compiled unconditionally and their_WIN32branches use desktop-only registry/token/version APIs (RegGetValueA,OpenProcessToken,GetFileVersionInfoA). Add a WindowsStore-safe implementation or exclude those helpers and their Manager startup calls for UWP; otherwise the explicitly supported UWP configuration fails before the transport setting matters.
if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
set(FOUNDRY_LOCAL_USE_TELEMETRY OFF CACHE BOOL "1DS telemetry is not available for UWP" FORCE)
endif()
| return HexEncode(digest, sizeof(digest)); | ||
| } | ||
|
|
||
| std::string Sha256String(std::string_view value) { |
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||
| // Licensed under the MIT License. | ||
|
|
||
| #include "http/HttpClient_Android.hpp" |
|
|
||
| ## Disabling Telemetry | ||
|
|
||
| Telemetry is opt-out through the SDK configuration: set the `disable_telemetry` option to `true` before creating the manager, and the SDK collects and uploads nothing. In the C++ SDK, this is `Configuration::SetDisableTelemetry(true)`. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 83 out of 83 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
sdk_v2/cpp/src/telemetry/telemetry_metadata.cc:84
neededincludes the terminating NUL, butouthas onlyneeded - 1writable characters while the conversion is told it may writeneeded. This writes past the string's size on every successful Windows version lookup. Allocateneeded, then remove the terminator after conversion.
sdk_v2/cpp/include/foundry_local/foundry_local_c.h:934- Appending this function to the version-1 table without incrementing
FOUNDRY_LOCAL_API_VERSIONdefeats the ABI version check. The updated C#/Python bindings still request v1, so when they load an older v1 native library they marshal/read this new slot past the old table and may call an arbitrary pointer instead of getting a clean version-mismatch failure. Introduce API v2 (and update each binding's requested version), while retaining the v1 table for older consumers.
/// Optional. Disable all telemetry. When true, the SDK collects and uploads no telemetry (no 1DS
/// uploader is created and no device identifier is written). Defaults to false (telemetry enabled).
FL_API_STATUS(SetDisableTelemetry, _In_ flConfiguration* config, bool disable);
sdk_v2/cpp/src/util/sha256.cc:87
- The only new assertion for this path checks the
c:prefix and output length, so both platform implementations could return an incorrect 64-character digest undetected. AddSha256Stringknown-vector tests (including"abc"and the empty string), matching the existingSha256Filevectors insha256_test.cc.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 83 out of 83 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (4)
sdk_v2/cpp/CMakeLists.txt:311
- The new Windows device-ID implementation calls
OpenProcessToken,RegGetValueA, andRegCreateKeyExA, all of which require Advapi32.lib. This target does not link it, so Windows builds—especially the newly supported--no_telemetryconfiguration where MSTelemetry cannot supply a transitive dependency—fail with unresolved externals.
target_link_libraries(${TARGET} ${LINK_SCOPE} dbghelp bcrypt version)
sdk_v2/cpp/CMakeLists.txt:52
- Turning off 1DS is not sufficient to keep the WindowsStore build valid.
device_id.ccandtelemetry_metadata.ccare still compiled unconditionally and their_WIN32branches call desktop-only registry and file-version APIs (RegGetValueA/RegCreateKeyExAandGetFileVersionInfoA). The UWP configuration therefore still compiles/links unsupported APIs; add WindowsStore-safe implementations or exclude these startup metadata paths for UWP.
if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
set(FOUNDRY_LOCAL_USE_TELEMETRY OFF CACHE BOOL "1DS telemetry is not available for UWP" FORCE)
endif()
sdk_v2/cpp/src/ep_detection/ep_detector.cc:269
RecordDownloadCompleteis called only after the combinedDownloadAndRegisteroperation has finished. Because that method measures elapsed time sinceRecordInitialState, the full operation is reported as a skipped download, while the immediately following successful register phase is reported as approximately 0 ms. Move the skipped download transition before invoking the combined bootstrapper so its elapsed time is attributed to registration.
tracker->RecordDownloadComplete(ActionStatus::kSkipped,
was_registered_before ? "Registered" : "Unknown");
tracker->RecordRegisterComplete(ActionStatus::kSuccess, "Registered");
sdk_v2/cpp/src/ep_detection/ep_detector.cc:281
- This failure path has the same phase-timing inversion: despite the comment saying the combined operation is recorded as registration, calling
RecordDownloadCompleteafter it returns puts all elapsed time indownload_duration_mswith statusSkipped, then records a near-zero failed registration. Advance to the register stage before calling the bootstrapper.
// Record the combined operation as the register phase rather than fabricating a download/register split.
tracker->RecordDownloadComplete(ActionStatus::kSkipped,
was_registered_before ? "Registered" : "Unknown");
tracker->RecordRegisterComplete(ActionStatus::kFailure,
was_registered_before ? "Registered" : "Unknown");
|
|
||
| Telemetry is limited to a small set of trace events emitted over the SDK's lifecycle: | ||
|
|
||
| - **Environment / device:** Foundry Local version, operating system, architecture, CPU details, and total memory, plus a device identifier. On Windows, Linux, and macOS the device identifier is a locally generated random UUID (**not** a hardware identifier such as a machine GUID); only a hashed form is transmitted, and it can be reset. On Android, Foundry Local uses the device identifier provided by the 1DS SDK instead of creating a separate Foundry-generated device id. |
| usage.correlation_id = context.correlation_id; | ||
| usage.indirect = context.indirect; | ||
| usage.stream = streaming; | ||
| usage.num_messages = CountRequestMessages(request); |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 85 out of 85 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (2)
sdk_v2/python/src/foundry_local_sdk/_native/build_cffi.py:477
- cffi’s
cdefparser does not accept the Cboolspelling (the file’s preprocessing contract at line 46 explicitly requires_Bool). This new vtable member therefore prevents_cffi_bindingsfrom being generated; use_Boolto match the other boolean ABI declarations.
sdk_v2/cpp/src/telemetry/one_ds_telemetry.cc:218 - This flag is set too late: if
GetLoggerreturns null or throws, the successfully created provider is never torn down/released (the null path drops anImplcontaining only a raw manager pointer, and the catch sees this flag as false). Mark initialization immediately afterCreateLogManagersucceeds and release the provider on the null-logger path.
| // RAII telemetry tracker — emits a "Download" event on destruction with whatever | ||
| // fields have been populated. Default status is kFailure so abrupt exits (exceptions) | ||
| // are recorded as failures; the happy path explicitly sets kSuccess / kSkipped. | ||
| std::unique_ptr<DownloadTracker> tracker; |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 85 out of 85 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (4)
sdk_v2/cpp/CMakeLists.txt:52
- Turning off 1DS is not sufficient to keep the WindowsStore target buildable.
device_id.ccandtelemetry_metadata.ccare still compiled unconditionally, and their_WIN32branches use UWP-forbidden APIs such as registry access/CreateMutexW and GetFileVersionInfoA/RtlGetVersion. WindowsStore also defines_WIN32, so this target will fail compilation or certification. Add UWP-safe implementations/guards (or source stubs) rather than only disabling the transport.
if(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
set(FOUNDRY_LOCAL_USE_TELEMETRY OFF CACHE BOOL "1DS telemetry is not available for UWP" FORCE)
endif()
sdk_v2/cpp/src/telemetry/one_ds_telemetry.cc:215
- If
GetLoggerreturns null (or throws), the successfully createdLogManagerProvideris never torn down or released becauselog_manager_initializedis set only afterward. Resettingimpl_also destroys the configuration that the live manager references, leaking its background state and leaving a dangling config reference. Mark the manager initialized immediately after successful creation and ensure every later failure calls teardown andLogManagerProvider::Releasebefore resettingimpl_.
sdk_v2/cpp/src/download/download_manager.cc:268 LockWaitMsis captured well after the in-process lock was acquired. On the cache-hit path it includes model-path/cache validation and even the user progress callback; on the normal path it also includes unrelated work before the cross-process wait. Slow callbacks therefore appear as lock contention. Accumulate only the durations spent acquiring the in-process and cross-process locks.
auto record_lock_wait = [&]() {
if (tracker != nullptr) {
tracker->SetLockWaitMs(std::chrono::duration_cast<std::chrono::milliseconds>(
clock::now() - lock_wait_start)
.count());
sdk_v2/python/src/foundry_local_sdk/_native/build_cffi.py:477
- Use
_Boolhere, as required by this file's cdef contract. cffi does not accept the C99boolkeyword in this declaration; the unchangedSession_RemoveToolDefinitiondeclaration at line 456 has the same violation, so both occurrences must be corrected orffi.cdef(...)fails before the native module can be built.
| OgaSetTelemetryEnabledFn ResolveOgaSetTelemetryEnabled() { | ||
| #ifdef _WIN32 | ||
| HMODULE genai = ::GetModuleHandleW(L"onnxruntime-genai.dll"); | ||
| if (genai == nullptr) { | ||
| return nullptr; | ||
| } | ||
| return reinterpret_cast<OgaSetTelemetryEnabledFn>(::GetProcAddress(genai, "OgaSetTelemetryEnabled")); | ||
| #else | ||
| return reinterpret_cast<OgaSetTelemetryEnabledFn>(dlsym(RTLD_DEFAULT, "OgaSetTelemetryEnabled")); |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 94 out of 96 changed files in this pull request and generated 8 comments.
Files not reviewed (2)
- sdk_v2/js/package-lock.json: Generated file
- www/package-lock.json: Generated file
Comments suppressed due to low confidence (2)
sdk_v2/cpp/src/telemetry/one_ds_telemetry.cc:488
- This uses the normal upload gate, so ProcessInfo is suppressed when
disable_telemetryis true. The constructor, SDK docs, and public configuration docs all promise that this one minimal event still uploads; this path needs to be the explicit gate bypass instead.
sdk_v2/cpp/src/download/download_manager.cc:255 - The tracker is created here, but exception classification starts only in the
tryat line 353. Cancellation while waiting for the cross-process lock or from the initial progress callback at line 349 therefore emitskFailureinstead of the newly definedkCanceledstatus. Enclose the complete post-tracker operation in the classification catch.
DownloadTracker tracker(info.model_id, user_agent, telemetry_);
|
|
||
| namespace { | ||
|
|
||
| constexpr uint64_t kStatusTelemetrySampleRate = 3'600; |
There was a problem hiding this comment.
This was an intentional change after owner review. The previous time-based sampler required a production-visible reset hook just to make tests deterministic; the owner asked to simplify this to deterministic request-count sampling at 1 per 3,600 requests. That keeps high-frequency health polling bounded without clock state/test hooks. No change needed here.
| config_values: dict[str, str] = { | ||
| "AppName": self.app_name, | ||
| "LogLevel": str(self.log_level), | ||
| "UserAgent": f"foundry-local-python/{__version__}", |
| } | ||
| } | ||
|
|
||
| if (segments >= 2) { |
| "version": "0.7.2", | ||
| "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", | ||
| "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", | ||
| "version": "0.6.0", |
| BlobDownloadStats stats; | ||
| DownloadBlobsToDirectory(*blob_downloader_, container.blob_sas_uri, | ||
| model_path, download_opts); | ||
| model_path, download_opts, stats); |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 91 out of 91 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (3)
sdk_v2/cpp/src/service/web_service.cc:53
- This is request-count sampling, not the stated “at most once per hour” sampling. For example, a health probe polling at 10 Hz emits every six minutes, while a slow probe may emit far less often. Gate on a monotonic last-emission timestamp (with an atomic compare/exchange) and a one-hour interval instead.
sdk_v2/cpp/src/download/download_manager.cc:255 - The tracker is created before locking, but the classification catch starts only after the cross-process wait and initial progress callback. Cancellation from
WaitForDirectoryLockor the callback at 0% therefore bypassesRecordException; the destructor emitsFailureinstead ofCanceled. Extend the classified catch to cover these pre-transfer cancellation paths.
// RAII telemetry tracker — emits a "Download" event on destruction with whatever
// fields have been populated. Default status is kFailure so abrupt exits (exceptions)
// are recorded as failures; the happy path explicitly sets kSuccess / kSkipped.
DownloadTracker tracker(info.model_id, user_agent, telemetry_);
sdk_v2/cpp/src/telemetry/telemetry_redaction.h:66
- Embedded single-segment absolute POSIX paths are not scrubbed:
open /secret failedproduces only one segment and is uploaded unchanged. A slash following whitespace or a quote is already an unambiguous path anchor, so redact that case without requiring two segments.
| std::string TelemetryDeviceId::HashForTelemetry(std::string_view raw_device_id) { | ||
| if (raw_device_id.empty()) { | ||
| return {}; | ||
| } | ||
| return "c:" + Fnv1aHex(std::string{kDeviceIdHashSalt} + std::string(raw_device_id)); |
| /// Compute SHA256 hash of an in-memory string and return it as uppercase hex string. | ||
| std::string Sha256String(std::string_view value); |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 92 out of 92 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
sdk_v2/cpp/src/download/download_manager.cc:367
- The initial 0% callback runs before this
try. If it cancels, the new tracker is destroyed with its defaultkFailurestatus and no Error event, whereas cancellations during blob transfer are correctly classified askCanceledby the catch below. Move this callback into the tracked try block so identical cancellation requests produce consistent telemetry.
BlobDownloadStats stats;
sdk_v2/cpp/include/foundry_local/foundry_local_c.h:934
- Appending this function changes the v1 configuration table, but
FOUNDRY_LOCAL_API_VERSIONand every binding's requested version remain 1. A new Python/C#/JS wrapper can therefore accept an older v1 native library and read this function pointer past the old table, leading to an invalid call instead of a clean version mismatch. Introduce a new API version/table and update all binding version constants (or otherwise add explicit table-size/version negotiation).
FL_API_STATUS(SetDisableTelemetry, _In_ flConfiguration* config, bool disable);
sdk_v2/cpp/src/service/web_service.cc:53
- This is request-count sampling, not an hourly throttle. A fast health checker can issue 3,600 requests in seconds and immediately emit another event, so the stated “at most once per hour” bound and telemetry volume are incorrect. Gate on elapsed steady-clock time instead.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 92 out of 92 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (4)
sdk_v2/cpp/src/service/web_service.cc:53
- This is request-count sampling, not the documented “at most once per hour” sampling. A busy health probe can emit many
ServiceStatusevents per hour (for example, 100 requests/s emits about every 36 seconds). Gate on elapsedsteady_clocktime instead of assuming one request per second.
sdk_v2/cpp/src/download/download_manager.cc:255 - The tracker is created before several throwing cancellation paths, but only exceptions from the later blob-download
trycallRecordException. An immediate 0% cancellation and cancellation while waiting for the cross-process lock therefore emitDownloadwith the defaultFailurestatus instead ofCanceled. Route all post-tracker exceptions throughRecordException(or explicitly setkCanceledon those paths).
// RAII telemetry tracker — emits a "Download" event on destruction with whatever
// fields have been populated. Default status is kFailure so abrupt exits (exceptions)
// are recorded as failures; the happy path explicitly sets kSuccess / kSkipped.
DownloadTracker tracker(info.model_id, user_agent, telemetry_);
sdk_v2/cpp/src/telemetry/ep_download_tracker.cc:78
RecordEventis outside the best-effort guard, so anITelemetryimplementation that throws here replaces the original EP bootstrap exception beforeEpDetectorcan rethrow it. Catch this call as well so telemetry cannot change the operation's failure semantics.
sdk_v2/cpp/src/telemetry/one_ds_telemetry.cc:195- If
GetLoggerthrows afterCreateLogManagersucceeds,log_manager_initializedis still false, so the catch block skipsFlushAndTeardown/Release. Becauseinitialized_also remains false, the destructor skips cleanup too, leaking the created 1DS manager. Mark the manager initialized immediately after the successful create (the null-logger branch already performs its own cleanup).
| } | ||
|
|
||
| bool ShouldRecordStatusTelemetry() { | ||
| return StatusTelemetryRequestCount().fetch_add(1, std::memory_order_relaxed) % kStatusTelemetrySampleRate == 0; |
| // RAII telemetry tracker — emits a "Download" event on destruction with whatever | ||
| // fields have been populated. Default status is kFailure so abrupt exits (exceptions) | ||
| // are recorded as failures; the happy path explicitly sets kSuccess / kSkipped. | ||
| DownloadTracker tracker(info.model_id, user_agent, telemetry_); |
| } catch (...) { | ||
| // Telemetry is best-effort and must not mask the original error path. | ||
| } | ||
| RecordEvent(status); |
| ort_api_->ReleaseStatus(status); | ||
| } | ||
|
|
||
| DisableOgaTelemetryIfAvailable(); |
| /// Optional. Disable non-essential telemetry. Foundry Local may still send a minimal ProcessInfo event. | ||
| /// Defaults to false (telemetry enabled). | ||
| FL_API_STATUS(SetDisableTelemetry, _In_ flConfiguration* config, bool disable); |
| } catch (...) { | ||
| } catch (const std::exception& e) { | ||
| record_stats(stats); | ||
| record_download_elapsed(); |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 91 out of 91 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (3)
sdk_v2/cpp/src/service/web_service.cc:53
- This is request-count sampling, not the documented “at most once per hour” throttle. The first request is always recorded, and a busy health probe records another event every 3,600 requests (for example, once per second at 3,600 requests/sec), so telemetry volume is unrelated to elapsed time. Gate this with
steady_clockand a process-wide last-emission timestamp instead.
sdk_v2/cpp/src/download/download_manager.cc:255 - The tracker defaults to
kFailure, but cancellation while waiting for the cross-process lock and cancellation from the initial 0% callback both throw before thetry/RecordExceptionblock below. Those normal user cancellations therefore emit a failed Download event (and no Error event classified askCanceled). Include all cancellation-capable setup after tracker creation in the classified exception path.
DownloadTracker tracker(info.model_id, user_agent, telemetry_);
sdk_v2/cpp/src/download/download_manager.cc:410
- On any exception this overwrites
stats.download_mswith elapsed time measured from before path computation and lock acquisition. A registry-resolution failure can therefore report seconds ofDownloadMseven though blob transfer never began, and lock time is double-counted in bothLockWaitTimeMsandDownloadTimeMs. Measure from immediately beforeDownloadBlobsToDirectory, and only use that fallback when the transfer phase actually started.
record_download_elapsed();
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 98 out of 98 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (3)
sdk_v2/cpp/src/telemetry/device_id.cc:500
- Concurrent processes recovering the same corrupt device-ID file can each succeed here:
renamereplaces the destination, so both processes retain different generated IDs and the last writer alone persists. That breaks the stable device-ID guarantee and splits telemetry identity for the earlier process. Serialize corrupt-file recovery across processes and re-read the winning value before returning.
sdk_v2/cpp/src/download/download_manager.cc:417 - On failure this overwrites
BlobDownloadStats::download_mswith elapsed time measured before cross-process lock acquisition, soDownloadTimeMsincludes lock waiting, registry resolution, and enumeration rather than transfer time. This makes failed-download telemetry incomparable with successful-download telemetry. Start the fallback timer immediately before blob transfer, or haveDownloadBlobsToDirectorypopulate its transfer duration on exceptional exits.
} catch (const std::exception& e) {
record_stats(stats);
record_download_elapsed();
sdk_v2/cpp/include/foundry_local/foundry_local_c.h:930
- This v2 function is currently placed before the
// End V1boundary, incorrectly documenting it as part of the v1 configuration table. Keep the v1 boundary before the appended member and add an End V2 marker so future version-specific table work preserves the ABI prefix correctly.
/// Optional. Disable non-essential telemetry. Foundry Local may still send a minimal ProcessInfo event.
/// Defaults to false (telemetry enabled).
FL_API_STATUS(SetDisableTelemetry, _In_ flConfiguration* config, bool disable);
| /// Returned by Manager_GetDiscoverableEps as a snapshot. Storage is owned by the Manager | ||
| /// and remains valid until Manager_GetDiscoverableEps is next called on the same thread. |
| ### Disabling Telemetry | ||
|
|
||
| Non-essential telemetry can be disabled as follows: | ||
|
|
||
| - **Disable via manager config.** Set the `disable_telemetry` option to `true` before creating the manager to disable non-essential telemetry. In the C++ SDK, this is `Configuration::SetDisableTelemetry(true)`. Foundry Local still sends a minimal ProcessInfo event. |
| case FOUNDRY_LOCAL_ERROR_OPERATION_CANCELLED: | ||
| return ActionStatus::kCanceled; | ||
| case FOUNDRY_LOCAL_ERROR_NETWORK: | ||
| return ActionStatus::kDependencyFailure; | ||
| default: |
| #ifdef _WIN32 | ||
| #define WIN32_LEAN_AND_MEAN | ||
| #include <windows.h> | ||
| #else |
Unify Foundry Local SDK v2 telemetry around always-built 1DS upload, stable device attribution, action/status classification, and language-specific user agents. Preserve ProcessInfo as the only manager opt-out upload exception while suppressing non-essential events. Files changed: - C++ telemetry backend, metadata, device ID, sampling, redaction, action/download/EP/catalog tracking - Manager/service/session/model paths that create telemetry contexts and service sessions - C++ API configuration comments/options for telemetry behavior without changing C ABI version - C#, JS, and Python bindings to set default UserAgent and DisableNonessentialTelemetry additional settings - C++ tests for telemetry statuses, attribution, hashing, redaction, downloads, EP cancellation, and web-service telemetry - Build/vcpkg pipeline wiring for always-on 1DS telemetry - Privacy documentation and CI opt-out updates Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 297bc539-5f7f-4a70-9411-48e91a5bf532
Keep shutdown state under the existing mutex and share the cache-removal paths so checkout, explicit eviction, clear, and shutdown cannot drift. Files changed: - sdk_v2/cpp/src/inferencing/session/session_manager.cc - sdk_v2/cpp/src/inferencing/session/session_manager.h Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 297bc539-5f7f-4a70-9411-48e91a5bf532
Avoid carrying raw catalog request URLs or response bodies through catalog failure messages. The outer catalog layer now logs coarse endpoint, region, and format fields with scrubbed exception text. Files changed: - sdk_v2/cpp/src/catalog/azure_catalog_client.cc - sdk_v2/cpp/src/catalog/azure_model_catalog.cc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 297bc539-5f7f-4a70-9411-48e91a5bf532
Pass the requested audio language hint through to telemetry instead of maintaining a client-side language list. Backend cleaning owns normalization for this field. Files changed: - sdk_v2/cpp/src/inferencing/generative/audio/audio_session.cc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 297bc539-5f7f-4a70-9411-48e91a5bf532
Keep raw catalog request URL and HTTP failure details in user-local errors and logs for troubleshooting. Uploaded CatalogFetch telemetry still uses the generic failure string from catalog_client.cc. Files changed: - sdk_v2/cpp/src/catalog/azure_catalog_client.cc - sdk_v2/cpp/src/catalog/azure_model_catalog.cc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 297bc539-5f7f-4a70-9411-48e91a5bf532
Keep local catalog refresh diagnostics consistent with the other catalog lookup paths by logging the configured URL and exception text locally. Uploaded CatalogFetch telemetry remains coarse. Files changed: - sdk_v2/cpp/src/catalog/azure_model_catalog.cc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 297bc539-5f7f-4a70-9411-48e91a5bf532
Match ORT's current context minimization by suppressing unneeded automatic 1DS semantic-context fields, keeping network context only on ProcessInfo, and avoiding duplicate uploaded ProcessInfo identity fields already available from logger context. Files changed: - sdk_v2/cpp/src/telemetry/one_ds_telemetry.cc - sdk_v2/cpp/src/telemetry/one_ds_telemetry.h - sdk_v2/cpp/src/telemetry/telemetry_context.h - sdk_v2/cpp/test/internal_api/telemetry_test.cc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 297bc539-5f7f-4a70-9411-48e91a5bf532
Foundry Local's ProcessInfo app, OS, process, and locale properties may differ from automatic 1DS context. Keep those explicit uploaded fields while retaining ORT-style suppression of unneeded automatic context. Files changed: - sdk_v2/cpp/src/telemetry/one_ds_telemetry.cc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 297bc539-5f7f-4a70-9411-48e91a5bf532
Download and HardwareInfo can benefit from 1DS network cost/provider/type context, so keep automatic network context available instead of globally clearing it after ProcessInfo. Continue suppressing unrelated automatic app/user context fields. Files changed: - sdk_v2/cpp/src/telemetry/one_ds_telemetry.cc - sdk_v2/cpp/src/telemetry/one_ds_telemetry.h - sdk_v2/cpp/src/telemetry/telemetry_context.h - sdk_v2/cpp/test/internal_api/telemetry_test.cc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 297bc539-5f7f-4a70-9411-48e91a5bf532
Summary
Adds SDK v2 telemetry core plumbing for Foundry Local:
Validation
python sdk_v2/cpp/build.py --build --config RelWithDebInfo --parallel 4python -m pytest sdk_v2/python/test/unit/test_configuration.py -qnpm run build:tsinsdk_v2/jsNotes
C# solution build is still blocked by the existing NU1603 warning-as-error dependency resolution issue for
Betalgo.Ranul.OpenAI/Microsoft.Extensions.Http.