Files
cline/apps/cli/script
632fce5071 feat(llms): sampled cline-provider AI SDK tracing via the host OTLP exporter (#13974)
* fix(llms): limit Langfuse telemetry to Cline provider

Refs ENG-2505

* fix(llms): include ClinePass in Langfuse telemetry

Refs ENG-2505

* feat(llms): sampled cline-provider AI SDK tracing via the host OTLP exporter

Enables the collector-relay tracing path: when the host telemetry service
has registered an OTLP tracer provider, cline-provider streams can emit
AI SDK spans without Langfuse credentials in the process.

- CLINE_TRACE_SAMPLE_PERCENT (default 0 = off) gates emission; sampling
  is a deterministic FNV-1a hash of the task/session id, so whole tasks
  sample together and retries decide identically.
- Metadata-only by default (recordInputs/recordOutputs false): models,
  tokens, timings, errors — no prompt or completion content. Content
  requires CLINE_TRACE_RECORD_CONTENT=true explicitly.
- The direct Langfuse path (env credentials, hub/internal) is unchanged:
  full content, every request, provider-gated as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(telemetry): client OTLP trace pipeline for cline-provider AI SDK spans

Extension side of the Langfuse collector relay:

- OpenTelemetryClientProvider gains a TracerProvider: when tracesExporter
  is configured, spans batch-export over OTLP (grpc / http/json /
  http/protobuf) to the same collector endpoint as logs/metrics.
  register() also installs the async context manager span parenting needs.
- tracesExporter plumbed through all three config sources: build constants
  (OTEL_TRACES_EXPORTER), runtime env (CLINE_OTEL_TRACES_EXPORTER), and
  remote config (openTelemetryTracesExporter) — the last is the no-release
  kill switch.
- SDK: a host that registers a traces exporter now defaults to 100% task
  sampling; CLINE_TRACE_SAMPLE_PERCENT reduces it (0 disables). Env reads
  are literal so build-time inlining works. Metadata-only stays the
  default; content still requires CLINE_TRACE_RECORD_CONTENT=true.
- CLI build inlines the new envs alongside the existing OTEL set.

No publish workflow sets OTEL_TRACES_EXPORTER yet, so shipped builds keep
tracing dark — activation is a separate one-line-per-workflow PR.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(llms): honor the global telemetry opt-out on the trace relay path

AI SDK spans bypass the ITelemetryService wrapper that enforces the
user's opt-out for events and metrics, so an opted-out user's prompts
would still have traced once a host registered an OTLP exporter.
resolveAiSdkTelemetry now re-checks the shared global settings file
(telemetryOptOut) per stream — covering startup state and mid-session
opt-outs across extension and CLI. The credentialed direct Langfuse
path is unchanged: it only activates on explicit operator config.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style: biome format

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(llms): one trace export path per host — direct Langfuse declines when an OTLP tracer is registered

Enablement is a per-stream boolean but export fan-out is processor-level:
attaching the direct LangfuseSpanProcessor to a host-registered tracer
provider ships every span twice (direct + collector relay). The direct
path now declines instead of cooperating when a recording provider
already owns the global slot; it only exports when it registers its own
provider. A host therefore configures exactly one of LANGFUSE_*
credentials or an OTLP traces exporter, and misconfiguring both is safe.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(hub): daemon-distinct trace identity for Langfuse hub coverage (#14034)

The detached hub daemon already has everything needed to emit AI SDK
traces: it ships in the same compiled binary as the CLI (so the publish
build's OTEL_TRACES_EXPORTER / CLINE_TRACE_RECORD_CONTENT inlining
reaches it), its telemetry handle flows into the hub server and the
scheduled-run handlers, core's OpenTelemetryProvider registers a global
tracer when the exporter is configured, and shutdown already flushes
with a hard deadline.

What was missing is identity: spans carry only the OTel resource, not
per-event metadata, so daemon-emitted traces were indistinguishable
from CLI-local ones (both service.name=cline). The daemon now declares
serviceName=cline-hub-daemon (+ serviceVersion), via new optional
serviceName/serviceVersion fields on the shared OpenTelemetryClientConfig.

Opt-out holds for the daemon unchanged: it is a same-machine, same-user
process, so the shared global settings file the per-stream gate reads is
the requesting user's own (shared/cloud topologies tracked in ENG-2525).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

* fix(llms): fail closed when the telemetry opt-out cannot be verified

The relay's opt-out guard treated every settings read failure as 'no
opt-out recorded', so a corrupted or unreadable settings file (including
a torn read of the non-atomic writer) silently re-enabled tracing for a
user who had opted out. Only a genuinely absent file (ENOENT, first run)
now reads as opted in; malformed JSON and every other read failure
disable the relay — consent that cannot be verified is not consent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(llms): identify the OTLP relay explicitly instead of inferring it from any recording tracer

'Some recording tracer exists' misclassified console-only tracing as the
collector relay (enabling the relay decision) and — for mutable
providers — newly suppressed the direct Langfuse attach that worked
before the one-path guard. The hosts that build tracer providers now
stamp a relay marker only when a real OTLP span processor was created
(console-only providers stay unmarked), and the trace decisions key off
that marker:

- relay path enables only behind a marked provider;
- the one-path decline fires only for marked providers — unmarked
  mutable providers keep the original cooperative attach behavior;
- a relay decline is no longer cached, so direct Langfuse gets to retry
  once a relay is disposed.

The marker lives on the provider instance (reached through the OTel API
global), which survives bundled module duplication where a module-level
registry would not.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* style: organize imports

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(telemetry): own and dispose remote trace clients across config updates

* fix(llms): wire per-call Langfuse telemetry and isolate direct export

* refactor(telemetry): reject unsupported providers before initialization

* test(desktop): raise sidecar vitest timeout above the module-graph import cost

The first test in each sidecar file pays the @cline/core → @cline/llms
import cost, which lands within ~100ms of the 5s default on CI runners
(commands-account failed at 5009ms; context.test.ts passed at 4923ms).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: abeatrix <beatrix@cline.bot>
2026-09-11 15:13:54 -07:00
..