chore: import upstream snapshot with attribution
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:03:19 +08:00
commit 91e75e620b
3227 changed files with 1307078 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
"""Core functionality shared across Cua components."""
__version__ = "0.1.8"
from cua_core.http import CUA_CLIENT_VERSION_HEADER, cua_version_headers
+35
View File
@@ -0,0 +1,35 @@
"""Shared HTTP utilities for CUA SDK requests."""
from functools import lru_cache
CUA_CLIENT_VERSION_HEADER = "X-Cua-Client-Version"
# CUA packages whose versions are included in the header value.
_CUA_PACKAGES = ("cua-agent", "cua-computer", "cua-core")
@lru_cache(maxsize=1)
def _build_version_string() -> str:
"""Return a composite version string like ``agent:0.4.0 computer:0.1.0 core:0.1.8``."""
from importlib.metadata import PackageNotFoundError, version
parts: list[str] = []
for pkg in _CUA_PACKAGES:
try:
short = pkg.removeprefix("cua-")
parts.append(f"{short}:{version(pkg)}")
except PackageNotFoundError:
continue
return " ".join(parts)
def cua_version_headers() -> dict[str, str]:
"""Return headers dict containing the CUA client version header.
Only installed CUA packages are included. If none are found the dict is
empty so it is always safe to unpack with ``**cua_version_headers()``.
"""
value = _build_version_string()
if not value:
return {}
return {CUA_CLIENT_VERSION_HEADER: value}
@@ -0,0 +1,38 @@
"""This module provides the core telemetry functionality for Cua libraries.
It provides a low-overhead way to collect anonymous usage data via PostHog
and operational metrics via OpenTelemetry.
"""
# OpenTelemetry instrumentation for Four Golden Signals
from cua_core.telemetry.otel import (
create_span,
instrument_async,
instrument_sync,
is_otel_enabled,
record_error,
record_operation,
record_tokens,
track_concurrent,
)
from cua_core.telemetry.posthog import (
destroy_telemetry_client,
is_telemetry_enabled,
record_event,
)
__all__ = [
# PostHog (product analytics)
"record_event",
"is_telemetry_enabled",
"destroy_telemetry_client",
# OpenTelemetry (operational metrics)
"is_otel_enabled",
"record_operation",
"record_error",
"record_tokens",
"track_concurrent",
"create_span",
"instrument_async",
"instrument_sync",
]
+537
View File
@@ -0,0 +1,537 @@
"""OpenTelemetry instrumentation for CUA SDK.
Provides metrics and tracing for the Four Golden Signals:
- Latency: Operation duration histograms
- Traffic: Operation counters
- Errors: Error counters
- Saturation: Concurrent operation gauges
"""
from __future__ import annotations
import atexit
import logging
import os
import time
from contextlib import contextmanager
from functools import wraps
from threading import Lock
from typing import Any, Callable, Dict, Generator, Optional, TypeVar, Union
logger = logging.getLogger("core.telemetry.otel")
# Type vars for decorator
F = TypeVar("F", bound=Callable[..., Any])
# Default OTEL endpoint
DEFAULT_OTEL_ENDPOINT = "https://otel.cua.ai"
# Lazy initialization state
_initialized = False
_init_failed = False
_init_lock = Lock()
# OTEL components (lazily initialized)
_meter: Optional[Any] = None
_tracer: Optional[Any] = None
_meter_provider: Optional[Any] = None
_tracer_provider: Optional[Any] = None
# Metrics (lazily initialized)
_operation_duration: Optional[Any] = None # Histogram
_operations_total: Optional[Any] = None # Counter
_errors_total: Optional[Any] = None # Counter
_concurrent_operations: Optional[Any] = None # UpDownCounter
_tokens_total: Optional[Any] = None # Counter
def is_otel_enabled() -> bool:
"""Check if OpenTelemetry is enabled.
Canonical opt-out: ``CUA_TELEMETRY_ENABLED=false``.
``CUA_TELEMETRY_DISABLED`` is deprecated — a warning is emitted on first
use and the value is honoured for backwards compatibility.
"""
import warnings
disabled_val = os.environ.get("CUA_TELEMETRY_DISABLED", "")
if disabled_val:
warnings.warn(
"CUA_TELEMETRY_DISABLED is deprecated. " "Use CUA_TELEMETRY_ENABLED=false instead.",
DeprecationWarning,
stacklevel=2,
)
if disabled_val.lower() in {"1", "true", "yes", "on"}:
return False
return os.environ.get("CUA_TELEMETRY_ENABLED", "true").lower() in {
"1",
"true",
"yes",
"on",
}
def _get_otel_endpoint() -> str:
"""Get the OTLP endpoint URL."""
return os.environ.get("CUA_OTEL_ENDPOINT", DEFAULT_OTEL_ENDPOINT)
def _get_service_name() -> str:
"""Get the service name for OTEL."""
return os.environ.get("CUA_OTEL_SERVICE_NAME", "cua-sdk")
def _initialize_otel() -> bool:
"""Initialize OpenTelemetry components.
Returns True if initialization succeeded, False otherwise.
Thread-safe via lock.
"""
global _initialized, _init_failed, _meter, _tracer, _meter_provider, _tracer_provider
global _operation_duration, _operations_total, _errors_total
global _concurrent_operations, _tokens_total
if _initialized:
return True
if _init_failed:
return False
with _init_lock:
# Double-check after acquiring lock
if _initialized:
return True
if _init_failed:
return False
if not is_otel_enabled():
logger.debug("OpenTelemetry disabled via CUA_TELEMETRY_DISABLED")
return False
try:
# Import OTEL packages lazily
from opentelemetry import metrics, trace
from opentelemetry.exporter.otlp.proto.http.metric_exporter import (
OTLPMetricExporter,
)
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
OTLPSpanExporter,
)
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
# Create resource with service info
resource = Resource.create(
{
"service.name": _get_service_name(),
"service.version": _get_sdk_version(),
}
)
endpoint = _get_otel_endpoint()
# Set up metrics
metric_exporter = OTLPMetricExporter(
endpoint=f"{endpoint}/v1/metrics",
)
metric_reader = PeriodicExportingMetricReader(
metric_exporter,
export_interval_millis=60000, # Export every 60 seconds
)
_meter_provider = MeterProvider(
resource=resource,
metric_readers=[metric_reader],
)
metrics.set_meter_provider(_meter_provider)
_meter = metrics.get_meter("cua-sdk", _get_sdk_version())
# Set up tracing
trace_exporter = OTLPSpanExporter(
endpoint=f"{endpoint}/v1/traces",
)
_tracer_provider = TracerProvider(resource=resource)
_tracer_provider.add_span_processor(BatchSpanProcessor(trace_exporter))
trace.set_tracer_provider(_tracer_provider)
_tracer = trace.get_tracer("cua-sdk", _get_sdk_version())
# Create metrics instruments
_operation_duration = _meter.create_histogram(
name="cua_sdk_operation_duration_seconds",
description="Duration of SDK operations in seconds",
unit="s",
)
_operations_total = _meter.create_counter(
name="cua_sdk_operations_total",
description="Total number of SDK operations",
unit="1",
)
_errors_total = _meter.create_counter(
name="cua_sdk_errors_total",
description="Total number of SDK errors",
unit="1",
)
_concurrent_operations = _meter.create_up_down_counter(
name="cua_sdk_concurrent_operations",
description="Number of concurrent SDK operations",
unit="1",
)
_tokens_total = _meter.create_counter(
name="cua_sdk_tokens_total",
description="Total tokens consumed",
unit="1",
)
# Register shutdown handler
atexit.register(_shutdown_otel)
_initialized = True
logger.info(f"OpenTelemetry initialized with endpoint: {endpoint}")
return True
except ImportError as e:
_init_failed = True
logger.debug(
f"OpenTelemetry packages not installed: {e}. "
"Install with: pip install opentelemetry-api opentelemetry-sdk "
"opentelemetry-exporter-otlp-proto-http"
)
return False
except Exception as e:
_init_failed = True
logger.warning(f"Failed to initialize OpenTelemetry: {e}")
return False
def _shutdown_otel() -> None:
"""Shutdown OpenTelemetry providers gracefully."""
global _meter_provider, _tracer_provider
try:
if _meter_provider is not None:
_meter_provider.shutdown()
if _tracer_provider is not None:
_tracer_provider.shutdown()
logger.debug("OpenTelemetry shutdown complete")
except Exception as e:
logger.debug(f"Error during OpenTelemetry shutdown: {e}")
def _get_sdk_version() -> str:
"""Get the CUA SDK version."""
try:
from cua_core import __version__
return __version__
except ImportError:
return "unknown"
# --- Public API ---
def record_operation(
operation: str,
duration_seconds: float,
status: str = "success",
model: Optional[str] = None,
os_type: Optional[str] = None,
**extra_attributes: Any,
) -> None:
"""Record an operation metric (latency + traffic).
Args:
operation: Operation name (e.g., "agent.run", "computer.action.click")
duration_seconds: Duration of the operation in seconds
status: Operation status ("success" or "error")
model: Model name if applicable
os_type: OS type if applicable
**extra_attributes: Additional attributes to record
"""
if not _initialize_otel():
return
attributes: Dict[str, str] = {
"operation": operation,
"status": status,
}
if model:
attributes["model"] = model
if os_type:
attributes["os_type"] = os_type
for key, value in extra_attributes.items():
if value is not None:
attributes[key] = str(value)
try:
if _operation_duration is not None:
_operation_duration.record(duration_seconds, attributes)
if _operations_total is not None:
_operations_total.add(1, attributes)
except Exception as e:
logger.debug(f"Failed to record operation metric: {e}")
def record_error(
error_type: str,
operation: str,
model: Optional[str] = None,
**extra_attributes: Any,
) -> None:
"""Record an error metric.
Args:
error_type: Type of error (e.g., "api_error", "timeout", "computer_error")
operation: Operation that failed
model: Model name if applicable
**extra_attributes: Additional attributes to record
"""
if not _initialize_otel():
return
attributes: Dict[str, str] = {
"error_type": error_type,
"operation": operation,
}
if model:
attributes["model"] = model
for key, value in extra_attributes.items():
if value is not None:
attributes[key] = str(value)
try:
if _errors_total is not None:
_errors_total.add(1, attributes)
except Exception as e:
logger.debug(f"Failed to record error metric: {e}")
def record_tokens(
prompt_tokens: int = 0,
completion_tokens: int = 0,
model: Optional[str] = None,
) -> None:
"""Record token usage metrics.
Args:
prompt_tokens: Number of prompt tokens
completion_tokens: Number of completion tokens
model: Model name
"""
if not _initialize_otel():
return
try:
if _tokens_total is not None:
if prompt_tokens > 0:
_tokens_total.add(
prompt_tokens,
{"token_type": "prompt", "model": model or "unknown"},
)
if completion_tokens > 0:
_tokens_total.add(
completion_tokens,
{"token_type": "completion", "model": model or "unknown"},
)
except Exception as e:
logger.debug(f"Failed to record token metric: {e}")
@contextmanager
def track_concurrent(operation_type: str) -> Generator[None, None, None]:
"""Context manager to track concurrent operations.
Args:
operation_type: Type of operation (e.g., "sessions", "runs")
Example:
with track_concurrent("agent_sessions"):
# session is active
pass
"""
if not _initialize_otel():
yield
return
attributes = {"operation_type": operation_type}
try:
if _concurrent_operations is not None:
_concurrent_operations.add(1, attributes)
except Exception as e:
logger.debug(f"Failed to increment concurrent counter: {e}")
try:
yield
finally:
try:
if _concurrent_operations is not None:
_concurrent_operations.add(-1, attributes)
except Exception as e:
logger.debug(f"Failed to decrement concurrent counter: {e}")
@contextmanager
def create_span(
name: str,
attributes: Optional[Dict[str, Any]] = None,
) -> Generator[Any, None, None]:
"""Create a trace span context.
Args:
name: Span name
attributes: Span attributes
Yields:
The span object (or None if tracing disabled)
Example:
with create_span("agent.run", {"model": "claude-3"}) as span:
# do work
if span:
span.set_attribute("steps", 5)
"""
if not _initialize_otel() or _tracer is None:
yield None
return
_yielded = False
try:
with _tracer.start_as_current_span(name, attributes=attributes) as span:
_yielded = True
yield span
except Exception as e:
if _yielded:
raise
logger.debug(f"Failed to create span: {e}")
yield None
def instrument_async(
operation: str,
model_attr: Optional[str] = None,
os_type_attr: Optional[str] = None,
) -> Callable[[F], F]:
"""Decorator to instrument an async function.
Records duration, success/error status, and creates a trace span.
Args:
operation: Operation name for metrics
model_attr: Attribute name to extract model from kwargs
os_type_attr: Attribute name to extract os_type from kwargs
Example:
@instrument_async("agent.run", model_attr="model")
async def run(self, prompt: str, model: str = "claude-3"):
...
"""
def decorator(func: F) -> F:
@wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> Any:
if not is_otel_enabled():
return await func(*args, **kwargs)
model = kwargs.get(model_attr) if model_attr else None
os_type = kwargs.get(os_type_attr) if os_type_attr else None
start_time = time.perf_counter()
status = "success"
error_type = None
with create_span(operation, {"model": model, "os_type": os_type}):
try:
result = await func(*args, **kwargs)
return result
except Exception as e:
status = "error"
error_type = type(e).__name__
raise
finally:
duration = time.perf_counter() - start_time
record_operation(
operation=operation,
duration_seconds=duration,
status=status,
model=model,
os_type=os_type,
)
if error_type:
record_error(
error_type=error_type,
operation=operation,
model=model,
)
return wrapper # type: ignore
return decorator
def instrument_sync(
operation: str,
model_attr: Optional[str] = None,
os_type_attr: Optional[str] = None,
) -> Callable[[F], F]:
"""Decorator to instrument a sync function.
Records duration, success/error status, and creates a trace span.
Args:
operation: Operation name for metrics
model_attr: Attribute name to extract model from kwargs
os_type_attr: Attribute name to extract os_type from kwargs
Example:
@instrument_sync("computer.screenshot")
def screenshot(self):
...
"""
def decorator(func: F) -> F:
@wraps(func)
def wrapper(*args: Any, **kwargs: Any) -> Any:
if not is_otel_enabled():
return func(*args, **kwargs)
model = kwargs.get(model_attr) if model_attr else None
os_type = kwargs.get(os_type_attr) if os_type_attr else None
start_time = time.perf_counter()
status = "success"
error_type = None
with create_span(operation, {"model": model, "os_type": os_type}):
try:
result = func(*args, **kwargs)
return result
except Exception as e:
status = "error"
error_type = type(e).__name__
raise
finally:
duration = time.perf_counter() - start_time
record_operation(
operation=operation,
duration_seconds=duration,
status=status,
model=model,
os_type=os_type,
)
if error_type:
record_error(
error_type=error_type,
operation=operation,
model=model,
)
return wrapper # type: ignore
return decorator
@@ -0,0 +1,250 @@
"""Telemetry client using PostHog for collecting anonymous usage data."""
from __future__ import annotations
import logging
import os
import sys
import uuid
from pathlib import Path
from typing import Any, Dict, List, Optional
import posthog
from cua_core import __version__
logger = logging.getLogger("core.telemetry")
# Public PostHog config for anonymous telemetry
# These values are intentionally public and meant for anonymous telemetry only
# https://posthog.com/docs/product-analytics/troubleshooting#is-it-ok-for-my-api-key-to-be-exposed-and-public
PUBLIC_POSTHOG_API_KEY = "phc_eSkLnbLxsnYFaXksif1ksbrNzYlJShr35miFLDppF14"
PUBLIC_POSTHOG_HOST = "https://eu.i.posthog.com"
class PostHogTelemetryClient:
"""Collects and reports telemetry data via PostHog."""
# Global singleton (class-managed)
_singleton: Optional["PostHogTelemetryClient"] = None
def __init__(self):
"""Initialize PostHog telemetry client."""
self.installation_id = self._get_or_create_installation_id()
self.initialized = False
self.queued_events: List[Dict[str, Any]] = []
# Log telemetry status on startup
if self.is_telemetry_enabled():
logger.info("Telemetry enabled")
# Initialize PostHog client if config is available
self._initialize_posthog()
else:
logger.info("Telemetry disabled")
@classmethod
def is_telemetry_enabled(cls) -> bool:
"""True if telemetry is currently active for this process.
Canonical opt-out: ``CUA_TELEMETRY_ENABLED=false``.
``CUA_TELEMETRY_DISABLED`` is deprecated — a warning is emitted on
first use and the value is honoured for backwards compatibility.
"""
# Deprecated env var: CUA_TELEMETRY_DISABLED
disabled_val = os.environ.get("CUA_TELEMETRY_DISABLED", "")
if disabled_val:
import warnings
warnings.warn(
"CUA_TELEMETRY_DISABLED is deprecated. " "Use CUA_TELEMETRY_ENABLED=false instead.",
DeprecationWarning,
stacklevel=2,
)
if disabled_val.lower() in {"1", "true", "yes", "on"}:
return False
return os.environ.get("CUA_TELEMETRY_ENABLED", "true").lower() in {
"1",
"true",
"yes",
"on",
}
def _get_or_create_installation_id(self) -> str:
"""Get or create a unique installation ID that persists across runs.
Stored in ``~/.config/cua/installation_id`` (XDG-compliant) so that
the ID survives package upgrades and is shared across venvs.
This ID is not tied to any personal information.
"""
try:
config_dir = Path.home() / ".config" / "cua"
config_dir.mkdir(parents=True, exist_ok=True)
id_file = config_dir / "installation_id"
# Try to read existing ID
if id_file.exists():
try:
stored_id = id_file.read_text().strip()
if stored_id: # Make sure it's not empty
logger.debug(f"Using existing installation ID: {stored_id}")
return stored_id
except Exception as e:
logger.debug(f"Error reading installation ID file: {e}")
# Create new ID
new_id = str(uuid.uuid4())
try:
id_file.write_text(new_id)
logger.debug(f"Created new installation ID: {new_id}")
return new_id
except Exception as e:
logger.warning(f"Could not write installation ID: {e}")
except Exception as e:
logger.warning(f"Error accessing core module directory: {e}")
# Last resort: Create a new in-memory ID
logger.warning("Using random installation ID (will not persist across runs)")
return str(uuid.uuid4())
def _initialize_posthog(self) -> bool:
"""Initialize the PostHog client with configuration.
Returns:
bool: True if initialized successfully, False otherwise
"""
if self.initialized:
return True
try:
# Allow overrides from environment for testing/region control
posthog.api_key = PUBLIC_POSTHOG_API_KEY
posthog.host = PUBLIC_POSTHOG_HOST
# Configure the client
posthog.debug = os.environ.get("CUA_TELEMETRY_DEBUG", "").lower() == "on"
# Log telemetry status
logger.info(
f"Initializing PostHog telemetry with installation ID: {self.installation_id}"
)
if posthog.debug:
logger.debug(f"PostHog API Key: {posthog.api_key}")
logger.debug(f"PostHog Host: {posthog.host}")
# Identify this installation
self._identify()
# Process any queued events
for event in self.queued_events:
posthog.capture(
distinct_id=self.installation_id,
event=event["event"],
properties=event["properties"],
)
self.queued_events = []
self.initialized = True
return True
except Exception as e:
logger.warning(f"Failed to initialize PostHog: {e}")
return False
def _identify(self) -> None:
"""Set up user properties for the current installation with PostHog."""
try:
properties = {
"version": __version__,
"is_ci": "CI" in os.environ,
"os": os.name,
"python_version": sys.version.split()[0],
}
logger.debug(
f"Setting up PostHog user properties for: {self.installation_id} with properties: {properties}"
)
# In the Python SDK, we capture an identification event instead of calling identify()
posthog.capture(
distinct_id=self.installation_id, event="$identify", properties={"$set": properties}
)
logger.info(f"Set up PostHog user properties for installation: {self.installation_id}")
except Exception as e:
logger.warning(f"Failed to set up PostHog user properties: {e}")
def record_event(self, event_name: str, properties: Optional[Dict[str, Any]] = None) -> None:
"""Record an event with optional properties.
Args:
event_name: Name of the event
properties: Event properties (must not contain sensitive data)
"""
# Respect runtime telemetry opt-out.
if not self.is_telemetry_enabled():
logger.debug("Telemetry disabled; event not recorded.")
return
event_properties = {"version": __version__, **(properties or {})}
logger.info(f"Recording event: {event_name} with properties: {event_properties}")
if self.initialized:
try:
posthog.capture(
distinct_id=self.installation_id, event=event_name, properties=event_properties
)
logger.info(f"Sent event to PostHog: {event_name}")
# Flush immediately to ensure delivery
posthog.flush()
except Exception as e:
logger.warning(f"Failed to send event to PostHog: {e}")
else:
# Queue the event for later
logger.info(f"PostHog not initialized, queuing event for later: {event_name}")
self.queued_events.append({"event": event_name, "properties": event_properties})
# Try to initialize now if not already
initialize_result = self._initialize_posthog()
logger.info(f"Attempted to initialize PostHog: {initialize_result}")
def flush(self) -> bool:
"""Flush any pending events to PostHog.
Returns:
bool: True if successful, False otherwise
"""
if not self.initialized and not self._initialize_posthog():
return False
try:
posthog.flush()
return True
except Exception as e:
logger.debug(f"Failed to flush PostHog events: {e}")
return False
@classmethod
def get_client(cls) -> "PostHogTelemetryClient":
"""Return the global PostHogTelemetryClient instance, creating it if needed."""
if cls._singleton is None:
cls._singleton = cls()
return cls._singleton
@classmethod
def destroy_client(cls) -> None:
"""Destroy the global PostHogTelemetryClient instance."""
cls._singleton = None
def destroy_telemetry_client() -> None:
"""Destroy the global PostHogTelemetryClient instance (class-managed)."""
PostHogTelemetryClient.destroy_client()
def is_telemetry_enabled() -> bool:
return PostHogTelemetryClient.is_telemetry_enabled()
def record_event(event_name: str, properties: Optional[Dict[str, Any]] | None = None) -> None:
"""Record an arbitrary PostHog event."""
PostHogTelemetryClient.get_client().record_event(event_name, properties or {})