|
| 1 | +"""Braintrust tracing integration for the Cursor Python SDK.""" |
| 2 | + |
| 3 | +import inspect |
| 4 | +from typing import Any |
| 5 | + |
| 6 | +from braintrust.logger import NOOP_SPAN, current_span, init_logger |
| 7 | + |
| 8 | +from .integration import CursorSDKIntegration |
| 9 | +from .patchers import AgentClosePatcher, AgentSendPatcher, AsyncAgentClosePatcher, AsyncAgentSendPatcher |
| 10 | + |
| 11 | + |
| 12 | +__all__ = ["CursorSDKIntegration", "setup_cursor_sdk", "wrap_cursor_sdk_agent"] |
| 13 | + |
| 14 | + |
| 15 | +def setup_cursor_sdk( |
| 16 | + api_key: str | None = None, |
| 17 | + project_id: str | None = None, |
| 18 | + project: str | None = None, |
| 19 | +) -> bool: |
| 20 | + """Patch Cursor SDK agent runs for Braintrust tracing.""" |
| 21 | + if current_span() == NOOP_SPAN: |
| 22 | + init_logger(project=project, api_key=api_key, project_id=project_id) |
| 23 | + return CursorSDKIntegration.setup() |
| 24 | + |
| 25 | + |
| 26 | +def wrap_cursor_sdk_agent(agent_class: Any) -> Any: |
| 27 | + """Instrument a Cursor ``Agent`` or ``AsyncAgent`` class in place. |
| 28 | +
|
| 29 | + Cursor traces model turns off ``Run``, not ``Agent``, so this also runs |
| 30 | + the full integration setup to patch the run lifecycle. |
| 31 | + """ |
| 32 | + CursorSDKIntegration.setup() |
| 33 | + # Both flavors spell these methods `send`/`close`, so the composite |
| 34 | + # patcher cannot tell them apart when wrapping a class directly. |
| 35 | + is_async = inspect.iscoroutinefunction(getattr(agent_class, "send", None)) |
| 36 | + send_patcher = AsyncAgentSendPatcher if is_async else AgentSendPatcher |
| 37 | + close_patcher = AsyncAgentClosePatcher if is_async else AgentClosePatcher |
| 38 | + send_patcher.wrap_target(agent_class) |
| 39 | + close_patcher.wrap_target(agent_class) |
| 40 | + return agent_class |
0 commit comments