feat(ai): add claude code agents sdk integration#5316
feat(ai): add claude code agents sdk integration#5316
Conversation
Semver Impact of This PR🟡 Minor (new features) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨Span Streaming
Other
Bug Fixes 🐛Google Genai
Span Streaming
Other
Internal Changes 🔧Fastmcp
Mcp
Other
🤖 This preview updates automatically when you update the PR. |
|
Excited about this!
|
| chat_span = get_start_span_function()( | ||
| op=OP.GEN_AI_CHAT, | ||
| name=f"claude-agent-sdk query {model}".strip(), | ||
| origin=ClaudeAgentSDKIntegration.origin, | ||
| ) | ||
| chat_span.__enter__() | ||
|
|
||
| with capture_internal_exceptions(): | ||
| _set_span_input_data(chat_span, prompt, options, integration) | ||
|
|
||
| collected_messages = [] | ||
| try: | ||
| async for message in original_func( | ||
| prompt=prompt, options=options, **kwargs | ||
| ): | ||
| collected_messages.append(message) | ||
| yield message | ||
| except Exception as exc: | ||
| _capture_exception(exc) | ||
| raise | ||
| finally: | ||
| with capture_internal_exceptions(): | ||
| _set_span_output_data(chat_span, collected_messages, integration) | ||
| chat_span.__exit__(None, None, None) | ||
|
|
||
| with capture_internal_exceptions(): | ||
| _process_tool_executions(collected_messages, integration) | ||
|
|
||
| with capture_internal_exceptions(): | ||
| _end_invoke_agent_span(invoke_span, collected_messages, integration) |
There was a problem hiding this comment.
does your agent know how to use a context manager 😄
| chat_span = get_start_span_function()( | |
| op=OP.GEN_AI_CHAT, | |
| name=f"claude-agent-sdk query {model}".strip(), | |
| origin=ClaudeAgentSDKIntegration.origin, | |
| ) | |
| chat_span.__enter__() | |
| with capture_internal_exceptions(): | |
| _set_span_input_data(chat_span, prompt, options, integration) | |
| collected_messages = [] | |
| try: | |
| async for message in original_func( | |
| prompt=prompt, options=options, **kwargs | |
| ): | |
| collected_messages.append(message) | |
| yield message | |
| except Exception as exc: | |
| _capture_exception(exc) | |
| raise | |
| finally: | |
| with capture_internal_exceptions(): | |
| _set_span_output_data(chat_span, collected_messages, integration) | |
| chat_span.__exit__(None, None, None) | |
| with capture_internal_exceptions(): | |
| _process_tool_executions(collected_messages, integration) | |
| with capture_internal_exceptions(): | |
| _end_invoke_agent_span(invoke_span, collected_messages, integration) | |
| chat_span = get_start_span_function()( | |
| op=OP.GEN_AI_CHAT, | |
| name=f"claude-agent-sdk query {model}".strip(), | |
| origin=ClaudeAgentSDKIntegration.origin, | |
| ) | |
| with chat_span() as span: | |
| with capture_internal_exceptions(): | |
| _set_span_input_data(span, prompt, options, integration) | |
| collected_messages = [] | |
| try: | |
| async for message in original_func( | |
| prompt=prompt, options=options, **kwargs | |
| ): | |
| collected_messages.append(message) | |
| yield message | |
| except Exception as exc: | |
| _capture_exception(exc) | |
| raise | |
| finally: | |
| with capture_internal_exceptions(): | |
| _set_span_output_data(span, collected_messages, integration) | |
| with capture_internal_exceptions(): | |
| _process_tool_executions(collected_messages, integration) | |
| with capture_internal_exceptions(): | |
| _end_invoke_agent_span(invoke_span, collected_messages, integration) |
| model = getattr(options, "model", "") if options else "" | ||
| invoke_span = _start_invoke_agent_span(prompt, options, integration) | ||
|
|
||
| chat_span = get_start_span_function()( |
There was a problem hiding this comment.
There's an invoke_agent span active here, why not just always start a span?
| messages = self._sentry_query_context.get("messages", []) | ||
| with capture_internal_exceptions(): | ||
| _set_span_output_data(chat_span, messages, integration) | ||
| chat_span.__exit__(None, None, None) | ||
| with capture_internal_exceptions(): | ||
| _end_invoke_agent_span(invoke_span, messages, integration) | ||
| self._sentry_query_context = {} |
There was a problem hiding this comment.
Exiting spans has caused many uncaught exceptions already 😄
| messages = self._sentry_query_context.get("messages", []) | |
| with capture_internal_exceptions(): | |
| _set_span_output_data(chat_span, messages, integration) | |
| chat_span.__exit__(None, None, None) | |
| with capture_internal_exceptions(): | |
| _end_invoke_agent_span(invoke_span, messages, integration) | |
| self._sentry_query_context = {} | |
| messages = self._sentry_query_context.get("messages", []) | |
| with capture_internal_exceptions(): | |
| _set_span_output_data(chat_span, messages, integration) | |
| chat_span.__exit__(None, None, None) | |
| _end_invoke_agent_span(invoke_span, messages, integration) | |
| self._sentry_query_context = {} |
| tool_uses = {} | ||
| tool_results = {} | ||
|
|
||
| for message in messages: |
There was a problem hiding this comment.
narrower than Anthropic message types, so this looks suspicious.
|
|
||
| def _extract_message_data(messages: list) -> dict: | ||
| """Extract relevant data from a list of messages.""" | ||
| data = { |
There was a problem hiding this comment.
use a class type instead of a dictionary for passing info between functions, stronger typing.
| ) | ||
|
|
||
| if _should_include_prompts(integration): | ||
| messages = [] |
There was a problem hiding this comment.
system prompts go in their own attribute.
| async for message in original_func( | ||
| prompt=prompt, options=options, **kwargs | ||
| ): | ||
| collected_messages.append(message) |
There was a problem hiding this comment.
your collecting user-held references here, leading to possible race conditions.
| def test_extract_text_returns_none_for_non_assistant(): | ||
| result = make_result_message(usage=None) | ||
| assert _extract_text_from_message(result) is None | ||
|
|
There was a problem hiding this comment.
We don't need all these unit tests, more work in the long run.
Instead, call the userspace API, and parameterize on as many input schemas as possible to achieve good test coverage.
Add instrumentation for the claude-agent-sdk package, which provides
a Python interface to interact with Claude Code CLI.
The integration captures:
Instrumented methods:
Closes TET-1743
Co-Authored-By: Claude Sonnet 4.5 noreply@anthropic.com