Skip to content

Add SDK v2 telemetry core#879

Open
bmehta001 wants to merge 9 commits into
mainfrom
bhamehta/flcore/1ds-telemetry-core
Open

Add SDK v2 telemetry core#879
bmehta001 wants to merge 9 commits into
mainfrom
bhamehta/flcore/1ds-telemetry-core

Conversation

@bmehta001

@bmehta001 bmehta001 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds SDK v2 telemetry core plumbing for Foundry Local:

  • always-build 1DS telemetry backend with CI/env/config upload gating
  • startup ProcessInfo and hardware inventory metadata
  • action, error, model/audio usage, download, catalog, and EP telemetry events
  • stable privacy-preserving device ID handling and telemetry redaction
  • user-agent attribution across C++, C#, JS, and Python SDK entry points
  • download/EP/status classification for client errors, cancellation, dependency failures, and timeouts
  • updated privacy documentation and CI telemetry opt-out

Validation

  • python sdk_v2/cpp/build.py --build --config RelWithDebInfo --parallel 4
  • focused C++ telemetry/download/EP/web-service/API tests
  • python -m pytest sdk_v2/python/test/unit/test_configuration.py -q
  • npm run build:ts in sdk_v2/js
  • local material review loop: current final pass reported no material findings

Notes

C# solution build is still blocked by the existing NU1603 warning-as-error dependency resolution issue for Betalgo.Ranul.OpenAI / Microsoft.Extensions.Http.

@vercel

vercel Bot commented Jul 13, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
foundry-local Ready Ready Preview, Comment Jul 25, 2026 4:43pm

Request Review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds 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 captured route_tracker is 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());

Comment thread sdk_v2/cpp/build.py Outdated
Comment on lines +481 to +482
if args.telemetry_token is not None:
command += [f"-DFOUNDRY_LOCAL_TELEMETRY_TOKEN={args.telemetry_token}"]
Comment on lines +26 to +30
// 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.
Comment on lines +215 to +217
// Re-throw to preserve existing semantics — the wrapper RAII guard above
// resets download_in_progress_; the tracker dtor records the EP event.
throw;
Comment on lines +231 to +233
if (tracker) {
tracker->RecordDownloadComplete(ActionStatus::kSuccess, "Installed");
tracker->RecordRegisterComplete(ActionStatus::kSuccess, "Registered");
Comment on lines +263 to +268
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_));
}
Comment on lines 251 to +252
body_ptr->Finish();
tracker.Remove(std::this_thread::get_id());
thread_tracker.Remove(std::this_thread::get_id());
Comment on lines +23 to +26
telemetry_.RecordAction(action_, status_, context_, duration_ms);

if (!model_id_.empty()) {
telemetry_.RecordModelId(action_, model_id_);
telemetry_.RecordModelId(action_, model_id_, status_, context_);
Comment on lines +20 to +24
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_);
}
Comment on lines +30 to +35
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);
}
Comment thread sdk_v2/cpp/build.py Outdated
Comment on lines +168 to +174
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.",
)
@bmehta001
bmehta001 requested a review from Copilot July 13, 2026 20:16
bmehta001 added a commit that referenced this pull request Jul 13, 2026
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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 49 out of 49 changed files in this pull request and generated 13 comments.

.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);
Comment thread sdk_v2/cpp/src/manager.cc Outdated
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();
Comment thread sdk_v2/cpp/src/manager.cc Outdated
web_service_.reset();
web_service_running_ = false;
bound_urls_.clear();
telemetry_->EndSession();
Comment thread sdk_v2/cpp/src/telemetry/telemetry.h Outdated
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;
Comment on lines +256 to +258
int64_t lock_wait_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
clock::now() - lock_wait_start)
.count();
Comment on lines +46 to +50
if (auto slash = rest.find('/'); slash == std::string::npos) {
out.endpoint = rest;
} else {
out.endpoint = rest.substr(0, slash);
path = rest.substr(slash + 1);
Comment thread sdk_v2/cpp/src/telemetry/one_ds_telemetry.cc Outdated
Comment thread sdk_v2/cpp/src/telemetry/one_ds_telemetry.cc Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 49 out of 49 changed files in this pull request and generated 11 comments.

Comment on lines +158 to +161
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);
Comment on lines +206 to +209
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);
Comment on lines +84 to +87
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);
Comment on lines +142 to +145
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);
Comment on lines +105 to +109
// 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();
Comment thread sdk_v2/cpp/src/telemetry/telemetry.h Outdated
Comment on lines +140 to +142
/// 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 {
Comment on lines +256 to +260
// 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) {
Comment on lines +84 to +88
bool TelemetryEnvironment::IsTruthyValue(std::string_view value) {
auto trimmed = Trim(value);
if (trimmed.empty()) {
return false;
}
Comment on lines +65 to +67
// 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.
Comment thread sdk_v2/cpp/src/download/download_manager.cc Outdated
bmehta001 added a commit that referenced this pull request Jul 13, 2026
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
@bmehta001
bmehta001 requested a review from Copilot July 13, 2026 23:00
bmehta001 added a commit that referenced this pull request Jul 13, 2026
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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 49 out of 49 changed files in this pull request and generated 7 comments.

// 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;
Comment on lines +157 to +158
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;
Comment on lines +186 to +187
auto model_infos = FetchAllModelInfosWithCachedModels(*client, cached_model_ids, logger_,
telemetry_, &base_info);
Comment thread sdk_v2/cpp/src/manager.cc Outdated
Comment on lines +486 to +491
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();
Comment on lines +84 to +85
bool TelemetryEnvironment::IsTruthyValue(std::string_view value) {
auto trimmed = Trim(value);
Comment on lines +65 to +67
// 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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 49 out of 49 changed files in this pull request and generated 9 comments.

Comment thread sdk_v2/cpp/vcpkg.json Outdated
"nlohmann-json",
"spdlog"
"spdlog",
{ "name": "sqlite3", "default-features": false }
Comment thread sdk_v2/cpp/src/telemetry/telemetry.cc Outdated
}
}

++telemetry_num_providers;
Comment on lines +175 to 177
auto response = HandleNonStreaming(session_ref, session_request);
tracker->SetStatus(ActionStatus::kSuccess);
return response;
Comment on lines +157 to 159
auto response = HandleNonStreaming(session_ref, session_request);
tracker->SetStatus(ActionStatus::kSuccess);
return response;
Comment thread sdk_v2/cpp/CMakeLists.txt Outdated
Comment on lines +402 to +406
# 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
Comment on lines +14 to +17
std::random_device rd;
std::mt19937_64 gen{(static_cast<uint64_t>(rd()) << 32) | rd()};
uint64_t hi = gen();
uint64_t lo = gen();
Comment on lines +89 to +92
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;
bmehta001 added a commit that referenced this pull request Jul 14, 2026
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
@bmehta001
bmehta001 requested a review from Copilot July 14, 2026 00:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 56 out of 56 changed files in this pull request and generated 2 comments.

Comment on lines +267 to +275
++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,
Comment on lines +617 to +619
TestHttpGet(urls[0] + "/status");
TestHttpGet(urls[0] + "/status");
TestHttpGet(urls[0] + "/status");
bmehta001 added a commit that referenced this pull request Jul 14, 2026
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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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=true still installs a telemetry sink: OneDsTelemetry mirrors every event to TelemetryLogger, and the no-1DS branch always uses TelemetryLogger directly. 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-op ITelemetry implementation 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

Comment on lines +83 to +85
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{};

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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_telemetry does not implement the documented “collects and uploads nothing” contract. OneDsTelemetry still sends every event to its TelemetryLogger mirror when disabled, while telemetry-off builds unconditionally select TelemetryLogger and 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_);

Comment on lines +932 to +934
/// 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);
Comment on lines +62 to +64
void EpDownloadTracker::Done() {
RecordEvent(ActionStatus::kSkipped);
}
Comment thread sdk_v2/cpp/docs/Privacy.md Outdated
Comment on lines +17 to +19
- **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.
Comment thread sdk_v2/cpp/src/telemetry/device_id.cc Outdated
Comment thread sdk_v2/cpp/CMakeLists.txt
Comment on lines 310 to 314
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")

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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 WIN32 target even though only the 1DS dependency is disabled for WindowsStore. device_id.cc and telemetry_metadata.cc still compile desktop-only APIs such as RegCreateKeyExA, GetFileVersionInfoA, and GlobalMemoryStatusEx, 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 DownloadAndRegister call this reports the download phase as Skipped, even when the bootstrapper actually downloaded a package. That makes DownloadStatus/DownloadTimeMs inaccurate 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;

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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

  • needed includes the terminating NUL because the input length is -1, but out reserves only needed - 1 bytes while the conversion is allowed to write needed. 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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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

  • needed includes the terminating NUL, but out reserves only needed - 1 bytes while WideCharToMultiByte is told it can write needed. On Windows this writes one byte past the string's size whenever an EXE version string is found. Allocate needed bytes; TrimVersionString will remove the trailing NUL.
    sdk_v2/cpp/docs/Privacy.md:17
  • The “What is collected” disclosure is incomplete relative to the upload implementation: ProcessInfo sends 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, and RegCreateKeyExA, all of which require Advapi32.lib. It is compiled even when FOUNDRY_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.cc and telemetry_metadata.cc are still compiled unconditionally and their _WIN32 branches 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) {

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 83 out of 83 changed files in this pull request and generated 2 comments.

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#include "http/HttpClient_Android.hpp"
Comment thread sdk_v2/cpp/docs/Privacy.md Outdated

## 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)`.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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

  • needed includes the terminating NUL, but out has only needed - 1 writable characters while the conversion is told it may write needed. This writes past the string's size on every successful Windows version lookup. Allocate needed, 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_VERSION defeats 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. Add Sha256String known-vector tests (including "abc" and the empty string), matching the existing Sha256File vectors in sha256_test.cc.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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, and RegCreateKeyExA, all of which require Advapi32.lib. This target does not link it, so Windows builds—especially the newly supported --no_telemetry configuration 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.cc and telemetry_metadata.cc are still compiled unconditionally and their _WIN32 branches call desktop-only registry and file-version APIs (RegGetValueA/RegCreateKeyExA and GetFileVersionInfoA). 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

  • RecordDownloadComplete is called only after the combined DownloadAndRegister operation has finished. Because that method measures elapsed time since RecordInitialState, 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 RecordDownloadComplete after it returns puts all elapsed time in download_duration_ms with status Skipped, 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");

Comment thread sdk_v2/python/src/foundry_local_sdk/_native/build_cffi.py Outdated
Comment thread sdk_v2/cpp/docs/Privacy.md Outdated

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);

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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 cdef parser does not accept the C bool spelling (the file’s preprocessing contract at line 46 explicitly requires _Bool). This new vtable member therefore prevents _cffi_bindings from being generated; use _Bool to match the other boolean ABI declarations.
    sdk_v2/cpp/src/telemetry/one_ds_telemetry.cc:218
  • This flag is set too late: if GetLogger returns null or throws, the successfully created provider is never torn down/released (the null path drops an Impl containing only a raw manager pointer, and the catch sees this flag as false). Mark initialization immediately after CreateLogManager succeeds 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;

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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.cc and telemetry_metadata.cc are still compiled unconditionally, and their _WIN32 branches 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 GetLogger returns null (or throws), the successfully created LogManagerProvider is never torn down or released because log_manager_initialized is set only afterward. Resetting impl_ 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 and LogManagerProvider::Release before resetting impl_.
    sdk_v2/cpp/src/download/download_manager.cc:268
  • LockWaitMs is 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 _Bool here, as required by this file's cdef contract. cffi does not accept the C99 bool keyword in this declaration; the unchanged Session_RemoveToolDefinition declaration at line 456 has the same violation, so both occurrences must be corrected or ffi.cdef(...) fails before the native module can be built.

Comment thread sdk_v2/cpp/src/manager.cc
Comment on lines +247 to +255
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"));

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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_telemetry is 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 try at line 353. Cancellation while waiting for the cross-process lock or from the initial progress callback at line 349 therefore emits kFailure instead of the newly defined kCanceled status. Enclose the complete post-tracker operation in the classification catch.
  DownloadTracker tracker(info.model_id, user_agent, telemetry_);

Comment thread sdk_v2/cpp/src/telemetry/one_ds_telemetry.cc Outdated
Comment thread sdk_v2/cpp/src/service/web_service.cc Outdated

namespace {

constexpr uint64_t kStatusTelemetrySampleRate = 3'600;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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) {
Comment thread sdk/js/package.json Outdated
Comment thread sdk_v2/js/package.json Outdated
Comment thread www/package-lock.json Outdated
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"version": "0.6.0",
Comment on lines +375 to +377
BlobDownloadStats stats;
DownloadBlobsToDirectory(*blob_downloader_, container.blob_sas_uri,
model_path, download_opts);
model_path, download_opts, stats);

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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 WaitForDirectoryLock or the callback at 0% therefore bypasses RecordException; the destructor emits Failure instead of Canceled. 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 failed produces 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.

Comment thread sdk_v2/cpp/src/telemetry/device_id.cc Outdated
Comment on lines +372 to +376
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));
Comment on lines +15 to +16
/// Compute SHA256 hash of an in-memory string and return it as uppercase hex string.
std::string Sha256String(std::string_view value);

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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 default kFailure status and no Error event, whereas cancellations during blob transfer are correctly classified as kCanceled by 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_VERSION and 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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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 ServiceStatus events per hour (for example, 100 requests/s emits about every 36 seconds). Gate on elapsed steady_clock time 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 try call RecordException. An immediate 0% cancellation and cancellation while waiting for the cross-process lock therefore emit Download with the default Failure status instead of Canceled. Route all post-tracker exceptions through RecordException (or explicitly set kCanceled on 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

  • RecordEvent is outside the best-effort guard, so an ITelemetry implementation that throws here replaces the original EP bootstrap exception before EpDetector can 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 GetLogger throws after CreateLogManager succeeds, log_manager_initialized is still false, so the catch block skips FlushAndTeardown/Release. Because initialized_ 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).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 92 out of 92 changed files in this pull request and generated 6 comments.

Comment thread sdk_v2/cpp/src/service/web_service.cc Outdated
}

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);
Comment thread sdk_v2/cpp/src/manager.cc
ort_api_->ReleaseStatus(status);
}

DisableOgaTelemetryIfAvailable();
Comment on lines +932 to +934
/// 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();

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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_clock and 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 the try/RecordException block below. Those normal user cancellations therefore emit a failed Download event (and no Error event classified as kCanceled). 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_ms with elapsed time measured from before path computation and lock acquisition. A registry-resolution failure can therefore report seconds of DownloadMs even though blob transfer never began, and lock time is double-counted in both LockWaitTimeMs and DownloadTimeMs. Measure from immediately before DownloadBlobsToDirectory, and only use that fallback when the transfer phase actually started.
    record_download_elapsed();

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

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: rename replaces 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_ms with elapsed time measured before cross-process lock acquisition, so DownloadTimeMs includes 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 have DownloadBlobsToDirectory populate 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 V1 boundary, 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);

Comment on lines +365 to +366
/// 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.
Comment thread sdk_v2/cpp/docs/Privacy.md Outdated
Comment on lines +19 to +23
### 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.
Comment on lines +95 to +99
case FOUNDRY_LOCAL_ERROR_OPERATION_CANCELLED:
return ActionStatus::kCanceled;
case FOUNDRY_LOCAL_ERROR_NETWORK:
return ActionStatus::kDependencyFailure;
default:
Comment on lines +17 to +20
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
bmehta001 and others added 9 commits July 24, 2026 12:31
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants