claude-agent-sdk 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/README.md +193 -6
- data/lib/claude_agent_sdk/instrumentation/otel.rb +307 -0
- data/lib/claude_agent_sdk/instrumentation.rb +6 -0
- data/lib/claude_agent_sdk/observer.rb +38 -0
- data/lib/claude_agent_sdk/types.rb +3 -2
- data/lib/claude_agent_sdk/version.rb +1 -1
- data/lib/claude_agent_sdk.rb +37 -2
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f58f68ab3fa3b156f329c2f1757840166e449cb74f0a8513522e7fddaa177daa
|
|
4
|
+
data.tar.gz: 4d2c107fce9aa21978c983b264799a0a795e2cc58bcac615a0a9786f982221d9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 656e3dc5eacd58edb5da35a0055771674e09fcff5a7754a79ab5fae4346ba34e4000e61d31d25743cf244cdb6241907b84696bd16ea63411af7dee70d9bc46af
|
|
7
|
+
data.tar.gz: 5da72d45e25794e720b011f19de10de076d1927e50863b13b1aa4ccde801dd6b697073e82025c281963752cb8408c1a0267cbe2ea7a9a233f3bee4c07e636b9e
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.13.0] - 2026-04-03
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
#### Observer Interface
|
|
13
|
+
- `Observer` module with `on_user_prompt`, `on_message`, `on_error`, `on_close` — all with no-op defaults
|
|
14
|
+
- `observers` option on `ClaudeAgentOptions` (default `[]`) — register observers for both `query()` and `Client`
|
|
15
|
+
- `resolve_observers` supports callable factories (lambdas) for thread-safe global defaults in Rails/Puma/Sidekiq
|
|
16
|
+
- `notify_observers` rescues per-observer errors so observers never crash the main pipeline
|
|
17
|
+
|
|
18
|
+
#### OpenTelemetry Instrumentation
|
|
19
|
+
- `ClaudeAgentSDK::Instrumentation::OTelObserver` — emits spans using `gen_ai.*` and OpenInference semantic conventions
|
|
20
|
+
- Span tree: `claude_agent.session` (root) → `claude_agent.generation` + `claude_agent.tool.*` (children)
|
|
21
|
+
- `langfuse.observation.type` set on all spans (`agent`/`generation`/`tool`) to enable Langfuse trace flow diagram
|
|
22
|
+
- `input.value`/`output.value` (OpenInference) for Langfuse Preview Input/Output fields
|
|
23
|
+
- `llm.token_count.*`, `llm.cost.total`, `llm.model_name` for full Langfuse cost/usage tracking
|
|
24
|
+
- `openinference.span.kind` (`AGENT`/`LLM`/`TOOL`) on all spans
|
|
25
|
+
- Events: `api_retry`, `rate_limit`, `tool_progress` recorded on root span
|
|
26
|
+
- Lazy `require 'opentelemetry'` — zero cost for users who don't use it
|
|
27
|
+
|
|
28
|
+
#### Examples
|
|
29
|
+
- `otel_langfuse_example.rb` — Langfuse-via-OTel setup with OTLP exporter
|
|
30
|
+
- `test_langfuse_otel.rb` — multi-tool integration test (Bash tool calls)
|
|
31
|
+
|
|
32
|
+
### Changed
|
|
33
|
+
- README: added Observability section with Langfuse setup guide, span attribute reference, custom observer example, Rails initializer patterns
|
|
34
|
+
- README: split sandbox into CLI settings vs sandbox-runtime rows in comparison table
|
|
35
|
+
- README: updated recommended gem version to `~> 0.13.0`
|
|
36
|
+
- CLAUDE.md: documented observer/instrumentation architecture
|
|
37
|
+
|
|
8
38
|
## [0.12.0] - 2026-04-01
|
|
9
39
|
|
|
10
40
|
Full Claude Code parity release — cross-referenced against the Claude Code source (`coreSchemas.ts`) and TypeScript SDK to bring every message type, hook event, and sandbox setting into the Ruby SDK.
|
data/README.md
CHANGED
|
@@ -27,19 +27,19 @@ All three SDKs share the same underlying mechanism: they spawn the `claude` CLI
|
|
|
27
27
|
| Permission callbacks | ✅ | ✅ | ✅ |
|
|
28
28
|
| Structured output | ✅ | ✅ | ✅ |
|
|
29
29
|
| All 24 message types | ✅ | partial | ✅ |
|
|
30
|
-
|
|
|
30
|
+
| [Sandbox](https://github.com/anthropic-experimental/sandbox-runtime) settings | ✅ | partial | ✅ |
|
|
31
31
|
| Bare mode (`--bare`) | ✅ | ✅ | ✅ |
|
|
32
32
|
| File checkpointing & rewind | ✅ | ✅ | ✅ |
|
|
33
33
|
| Session browsing & mutations | ✅ | ✅ | ✅ |
|
|
34
34
|
| Programmatic subagents | ✅ | ✅ | ✅ |
|
|
35
35
|
| Bundled CLI binary | ✅ | ✅ | — (install `claude` separately) |
|
|
36
|
+
| Observability (OTel / Langfuse) | via [Arize](https://github.com/Arize-ai/openinference) | — | ✅ (built-in) |
|
|
36
37
|
| Custom transport (pluggable I/O) | — | — | ✅ |
|
|
37
38
|
| Rails integration | — | — | ✅ |
|
|
38
|
-
| Global config defaults | — | — | ✅ |
|
|
39
39
|
|
|
40
|
-
**Where Ruby goes further:** Custom transport support lets you swap the subprocess for any I/O layer (e.g., connect to a remote Claude Code instance over SSH or a container). Rails integration provides a `configure` block for initializers and plays well with ActionCable for real-time streaming.
|
|
40
|
+
**Where Ruby goes further:** Built-in OpenTelemetry observer with Langfuse flow diagram support — no third-party instrumentation library needed. Custom transport support lets you swap the subprocess for any I/O layer (e.g., connect to a remote Claude Code instance over SSH or a container). Rails integration provides a `configure` block for initializers with thread-safe observer factories, and plays well with ActionCable for real-time streaming. Full typed coverage for all 24 CLI message types and all 27 hook events — some of which the Python SDK hasn't typed yet.
|
|
41
41
|
|
|
42
|
-
**What's missing:** The Ruby gem does not bundle the `claude` CLI binary
|
|
42
|
+
**What's missing:** The Ruby gem does not bundle the `claude` CLI binary (`npm install -g @anthropic-ai/claude-code`).
|
|
43
43
|
|
|
44
44
|
<details>
|
|
45
45
|
<summary><strong>Implementation differences from the official SDKs</strong></summary>
|
|
@@ -89,6 +89,7 @@ All three SDKs spawn `claude` CLI as a subprocess with stream-JSON over stdin/st
|
|
|
89
89
|
- [File Checkpointing & Rewind](#file-checkpointing--rewind)
|
|
90
90
|
- [Session Browsing](#session-browsing)
|
|
91
91
|
- [Session Mutations](#session-mutations)
|
|
92
|
+
- [Observability (OpenTelemetry / Langfuse)](#observability-opentelemetry--langfuse)
|
|
92
93
|
- [Rails Integration](#rails-integration)
|
|
93
94
|
- [Types](#types)
|
|
94
95
|
- [Error Handling](#error-handling)
|
|
@@ -105,7 +106,7 @@ Add this line to your application's Gemfile:
|
|
|
105
106
|
gem 'claude-agent-sdk', github: 'ya-luotao/claude-agent-sdk-ruby'
|
|
106
107
|
|
|
107
108
|
# Or use a stable version from RubyGems
|
|
108
|
-
gem 'claude-agent-sdk', '~> 0.
|
|
109
|
+
gem 'claude-agent-sdk', '~> 0.13.0'
|
|
109
110
|
```
|
|
110
111
|
|
|
111
112
|
And then execute:
|
|
@@ -822,7 +823,7 @@ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
|
|
|
822
823
|
|
|
823
824
|
## Sandbox Settings
|
|
824
825
|
|
|
825
|
-
|
|
826
|
+
Configure [sandbox-runtime](https://github.com/anthropic-experimental/sandbox-runtime) restrictions (network policy, filesystem access) via the CLI's `--sandbox` flag. The CLI handles OS-level process isolation using `srt`.
|
|
826
827
|
|
|
827
828
|
```ruby
|
|
828
829
|
sandbox = ClaudeAgentSDK::SandboxSettings.new(
|
|
@@ -1006,6 +1007,137 @@ ClaudeAgentSDK.tag_session(
|
|
|
1006
1007
|
|
|
1007
1008
|
> **Note:** Session mutations use append-only JSONL writes with `O_WRONLY | O_APPEND` (no `O_CREAT`) for TOCTOU safety. They are safe to call while the session is open in a CLI process.
|
|
1008
1009
|
|
|
1010
|
+
## Observability (OpenTelemetry / Langfuse)
|
|
1011
|
+
|
|
1012
|
+
The SDK includes a built-in **observer interface** and an **OpenTelemetry observer** for tracing agent sessions. Traces are emitted using standard `gen_ai.*` semantic conventions, compatible with Langfuse, Jaeger, Datadog, and any OTel backend.
|
|
1013
|
+
|
|
1014
|
+
### How It Works
|
|
1015
|
+
|
|
1016
|
+
Register observers via `ClaudeAgentOptions`. The SDK calls `on_message` for every parsed message in both `query()` and `Client`, and `on_close` when the session ends. Observer errors are silently rescued so they never crash your application.
|
|
1017
|
+
|
|
1018
|
+
```
|
|
1019
|
+
claude_agent.session (root span — one per query/session)
|
|
1020
|
+
├── claude_agent.generation (per AssistantMessage, with model + token usage)
|
|
1021
|
+
├── claude_agent.tool.Bash (per tool call, open on ToolUseBlock, close on ToolResultBlock)
|
|
1022
|
+
├── claude_agent.tool.Read
|
|
1023
|
+
├── claude_agent.generation
|
|
1024
|
+
└── ...
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
### Setup with Langfuse
|
|
1028
|
+
|
|
1029
|
+
**1. Install the OTel gems** (not bundled with the SDK — you choose your exporter):
|
|
1030
|
+
|
|
1031
|
+
```bash
|
|
1032
|
+
gem install opentelemetry-sdk opentelemetry-exporter-otlp
|
|
1033
|
+
```
|
|
1034
|
+
|
|
1035
|
+
Or add to your Gemfile:
|
|
1036
|
+
|
|
1037
|
+
```ruby
|
|
1038
|
+
gem 'opentelemetry-sdk', '~> 1.4'
|
|
1039
|
+
gem 'opentelemetry-exporter-otlp', '~> 0.28'
|
|
1040
|
+
```
|
|
1041
|
+
|
|
1042
|
+
**2. Configure the OTel SDK** to export to your Langfuse instance:
|
|
1043
|
+
|
|
1044
|
+
```ruby
|
|
1045
|
+
require 'base64'
|
|
1046
|
+
require 'opentelemetry/sdk'
|
|
1047
|
+
require 'opentelemetry/exporter/otlp'
|
|
1048
|
+
|
|
1049
|
+
# Langfuse authenticates via Basic Auth over OTLP
|
|
1050
|
+
public_key = ENV['LANGFUSE_PUBLIC_KEY']
|
|
1051
|
+
secret_key = ENV['LANGFUSE_SECRET_KEY']
|
|
1052
|
+
auth = Base64.strict_encode64("#{public_key}:#{secret_key}")
|
|
1053
|
+
|
|
1054
|
+
# Self-hosted or cloud: https://cloud.langfuse.com (EU) / https://us.cloud.langfuse.com (US)
|
|
1055
|
+
langfuse_host = ENV.fetch('LANGFUSE_HOST', 'https://cloud.langfuse.com')
|
|
1056
|
+
|
|
1057
|
+
OpenTelemetry::SDK.configure do |c|
|
|
1058
|
+
c.service_name = 'my-agent-app'
|
|
1059
|
+
c.add_span_processor(
|
|
1060
|
+
OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
|
|
1061
|
+
OpenTelemetry::Exporter::OTLP::Exporter.new(
|
|
1062
|
+
endpoint: "#{langfuse_host}/api/public/otel/v1/traces",
|
|
1063
|
+
headers: {
|
|
1064
|
+
'Authorization' => "Basic #{auth}",
|
|
1065
|
+
'x-langfuse-ingestion-version' => '4'
|
|
1066
|
+
}
|
|
1067
|
+
)
|
|
1068
|
+
)
|
|
1069
|
+
)
|
|
1070
|
+
end
|
|
1071
|
+
```
|
|
1072
|
+
|
|
1073
|
+
**3. Create the observer and run a query:**
|
|
1074
|
+
|
|
1075
|
+
```ruby
|
|
1076
|
+
require 'claude_agent_sdk'
|
|
1077
|
+
require 'claude_agent_sdk/instrumentation'
|
|
1078
|
+
|
|
1079
|
+
observer = ClaudeAgentSDK::Instrumentation::OTelObserver.new(
|
|
1080
|
+
'langfuse.session.id' => 'my-session-123', # optional: group traces by session
|
|
1081
|
+
'user.id' => 'user-42' # optional: tag with user ID
|
|
1082
|
+
)
|
|
1083
|
+
|
|
1084
|
+
options = ClaudeAgentSDK::ClaudeAgentOptions.new(
|
|
1085
|
+
observers: [observer],
|
|
1086
|
+
allowed_tools: ['Bash', 'Read'],
|
|
1087
|
+
permission_mode: 'bypassPermissions'
|
|
1088
|
+
)
|
|
1089
|
+
|
|
1090
|
+
ClaudeAgentSDK.query(prompt: "List files in /tmp", options: options) do |msg|
|
|
1091
|
+
if msg.is_a?(ClaudeAgentSDK::AssistantMessage)
|
|
1092
|
+
msg.content.each do |block|
|
|
1093
|
+
puts block.text if block.is_a?(ClaudeAgentSDK::TextBlock)
|
|
1094
|
+
end
|
|
1095
|
+
end
|
|
1096
|
+
end
|
|
1097
|
+
|
|
1098
|
+
# For long-running apps, flush before exit:
|
|
1099
|
+
# OpenTelemetry.tracer_provider.shutdown
|
|
1100
|
+
```
|
|
1101
|
+
|
|
1102
|
+
### Span Attributes
|
|
1103
|
+
|
|
1104
|
+
The OTel observer sets attributes using both `gen_ai.*` (OTel GenAI) and OpenInference conventions for maximum backend compatibility:
|
|
1105
|
+
|
|
1106
|
+
| Span | Type | Key Attributes |
|
|
1107
|
+
|------|------|----------------|
|
|
1108
|
+
| `claude_agent.session` | `agent` | `gen_ai.system`, `gen_ai.request.model`, `session.id`, `input.value`, `output.value`, `gen_ai.usage.cost`, `llm.cost.total` |
|
|
1109
|
+
| `claude_agent.generation` | `generation` | `gen_ai.response.model`, `gen_ai.usage.input_tokens`, `gen_ai.usage.output_tokens`, `output.value` |
|
|
1110
|
+
| `claude_agent.tool.*` | `tool` | `tool.name`, `input.value`, `output.value` |
|
|
1111
|
+
|
|
1112
|
+
Events (`api_retry`, `rate_limit`, `tool_progress`) are recorded on the root span.
|
|
1113
|
+
|
|
1114
|
+
The `langfuse.observation.type` attribute is set on each span (`agent`/`generation`/`tool`) to enable Langfuse's **trace flow diagram** (DAG graph visualization).
|
|
1115
|
+
|
|
1116
|
+
### Custom Observers
|
|
1117
|
+
|
|
1118
|
+
Implement the `Observer` module to build your own instrumentation:
|
|
1119
|
+
|
|
1120
|
+
```ruby
|
|
1121
|
+
class MyObserver
|
|
1122
|
+
include ClaudeAgentSDK::Observer
|
|
1123
|
+
|
|
1124
|
+
def on_message(message)
|
|
1125
|
+
case message
|
|
1126
|
+
when ClaudeAgentSDK::ResultMessage
|
|
1127
|
+
puts "Cost: $#{message.total_cost_usd}, Tokens: #{message.usage}"
|
|
1128
|
+
end
|
|
1129
|
+
end
|
|
1130
|
+
|
|
1131
|
+
def on_close
|
|
1132
|
+
puts "Session ended"
|
|
1133
|
+
end
|
|
1134
|
+
end
|
|
1135
|
+
|
|
1136
|
+
options = ClaudeAgentSDK::ClaudeAgentOptions.new(observers: [MyObserver.new])
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
For a complete multi-tool example, see [examples/otel_langfuse_example.rb](examples/otel_langfuse_example.rb).
|
|
1140
|
+
|
|
1009
1141
|
## Rails Integration
|
|
1010
1142
|
|
|
1011
1143
|
The SDK integrates well with Rails applications. Here are common patterns:
|
|
@@ -1153,6 +1285,55 @@ options = ClaudeAgentSDK::ClaudeAgentOptions.new(
|
|
|
1153
1285
|
)
|
|
1154
1286
|
```
|
|
1155
1287
|
|
|
1288
|
+
### Observability in Rails
|
|
1289
|
+
|
|
1290
|
+
Add OpenTelemetry tracing to your Rails app with a single initializer:
|
|
1291
|
+
|
|
1292
|
+
```ruby
|
|
1293
|
+
# config/initializers/opentelemetry.rb
|
|
1294
|
+
require 'base64'
|
|
1295
|
+
require 'opentelemetry/sdk'
|
|
1296
|
+
require 'opentelemetry/exporter/otlp'
|
|
1297
|
+
|
|
1298
|
+
if ENV['LANGFUSE_PUBLIC_KEY'].present?
|
|
1299
|
+
auth = Base64.strict_encode64("#{ENV['LANGFUSE_PUBLIC_KEY']}:#{ENV['LANGFUSE_SECRET_KEY']}")
|
|
1300
|
+
langfuse_host = ENV.fetch('LANGFUSE_HOST', 'https://cloud.langfuse.com')
|
|
1301
|
+
|
|
1302
|
+
OpenTelemetry::SDK.configure do |c|
|
|
1303
|
+
c.service_name = Rails.application.class.module_parent_name.underscore
|
|
1304
|
+
c.add_span_processor(
|
|
1305
|
+
OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
|
|
1306
|
+
OpenTelemetry::Exporter::OTLP::Exporter.new(
|
|
1307
|
+
endpoint: "#{langfuse_host}/api/public/otel/v1/traces",
|
|
1308
|
+
headers: {
|
|
1309
|
+
'Authorization' => "Basic #{auth}",
|
|
1310
|
+
'x-langfuse-ingestion-version' => '4'
|
|
1311
|
+
}
|
|
1312
|
+
)
|
|
1313
|
+
)
|
|
1314
|
+
)
|
|
1315
|
+
end
|
|
1316
|
+
end
|
|
1317
|
+
```
|
|
1318
|
+
|
|
1319
|
+
```ruby
|
|
1320
|
+
# config/initializers/claude_agent_sdk.rb
|
|
1321
|
+
require 'claude_agent_sdk/instrumentation'
|
|
1322
|
+
|
|
1323
|
+
ClaudeAgentSDK.configure do |config|
|
|
1324
|
+
config.default_options = {
|
|
1325
|
+
permission_mode: 'bypassPermissions',
|
|
1326
|
+
observers: ENV['LANGFUSE_PUBLIC_KEY'].present? ? [
|
|
1327
|
+
# Use a lambda so each query gets a fresh observer instance (thread-safe).
|
|
1328
|
+
# A single shared instance would have its span state clobbered by concurrent requests.
|
|
1329
|
+
-> { ClaudeAgentSDK::Instrumentation::OTelObserver.new }
|
|
1330
|
+
] : []
|
|
1331
|
+
}
|
|
1332
|
+
end
|
|
1333
|
+
```
|
|
1334
|
+
|
|
1335
|
+
Then every `ClaudeAgentSDK.query` and `Client` session automatically gets traced — no per-call wiring needed. The lambda factory ensures each request gets its own observer with isolated span state, safe for concurrent Puma/Sidekiq workers.
|
|
1336
|
+
|
|
1156
1337
|
For complete examples, see:
|
|
1157
1338
|
- [examples/rails_actioncable_example.rb](examples/rails_actioncable_example.rb)
|
|
1158
1339
|
- [examples/rails_background_job_example.rb](examples/rails_background_job_example.rb)
|
|
@@ -1499,6 +1680,12 @@ See the [Claude Code documentation](https://docs.anthropic.com/en/docs/claude-co
|
|
|
1499
1680
|
| [examples/fallback_model_example.rb](examples/fallback_model_example.rb) | Fallback model configuration |
|
|
1500
1681
|
| [examples/extended_thinking_example.rb](examples/extended_thinking_example.rb) | Extended thinking (API parity) |
|
|
1501
1682
|
|
|
1683
|
+
### Observability
|
|
1684
|
+
|
|
1685
|
+
| Example | Description |
|
|
1686
|
+
|---------|-------------|
|
|
1687
|
+
| [examples/otel_langfuse_example.rb](examples/otel_langfuse_example.rb) | OpenTelemetry tracing with Langfuse backend |
|
|
1688
|
+
|
|
1502
1689
|
### Rails Integration
|
|
1503
1690
|
|
|
1504
1691
|
| Example | Description |
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require_relative '../observer'
|
|
5
|
+
|
|
6
|
+
module ClaudeAgentSDK
|
|
7
|
+
module Instrumentation
|
|
8
|
+
# OpenTelemetry observer that emits spans for Claude Agent SDK messages.
|
|
9
|
+
#
|
|
10
|
+
# Uses standard gen_ai.* semantic conventions recognized by Langfuse, Datadog,
|
|
11
|
+
# Jaeger, and other OTel-compatible backends.
|
|
12
|
+
#
|
|
13
|
+
# Requires the `opentelemetry-api` gem at runtime. Users must configure
|
|
14
|
+
# `opentelemetry-sdk` and an exporter (e.g., `opentelemetry-exporter-otlp`)
|
|
15
|
+
# themselves before creating this observer.
|
|
16
|
+
#
|
|
17
|
+
# @example With Langfuse via OTLP
|
|
18
|
+
# require 'opentelemetry/sdk'
|
|
19
|
+
# require 'opentelemetry/exporter/otlp'
|
|
20
|
+
# require 'claude_agent_sdk/instrumentation'
|
|
21
|
+
#
|
|
22
|
+
# OpenTelemetry::SDK.configure do |c|
|
|
23
|
+
# c.service_name = 'my-app'
|
|
24
|
+
# c.add_span_processor(
|
|
25
|
+
# OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
|
|
26
|
+
# OpenTelemetry::Exporter::OTLP::Exporter.new(
|
|
27
|
+
# endpoint: 'https://cloud.langfuse.com/api/public/otel/v1/traces',
|
|
28
|
+
# headers: { 'Authorization' => "Basic #{auth}" }
|
|
29
|
+
# )
|
|
30
|
+
# )
|
|
31
|
+
# )
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# observer = ClaudeAgentSDK::Instrumentation::OTelObserver.new
|
|
35
|
+
# options = ClaudeAgentSDK::ClaudeAgentOptions.new(observers: [observer])
|
|
36
|
+
# ClaudeAgentSDK.query(prompt: "Hello", options: options) { |msg| ... }
|
|
37
|
+
class OTelObserver
|
|
38
|
+
include ClaudeAgentSDK::Observer
|
|
39
|
+
|
|
40
|
+
TRACER_NAME = 'claude_agent_sdk'
|
|
41
|
+
MAX_ATTRIBUTE_LENGTH = 4096
|
|
42
|
+
|
|
43
|
+
def initialize(tracer_name: TRACER_NAME, **default_attributes)
|
|
44
|
+
require 'opentelemetry'
|
|
45
|
+
@tracer = OpenTelemetry.tracer_provider.tracer(
|
|
46
|
+
tracer_name,
|
|
47
|
+
defined?(ClaudeAgentSDK::VERSION) ? ClaudeAgentSDK::VERSION : '0.0.0'
|
|
48
|
+
)
|
|
49
|
+
@default_attributes = default_attributes
|
|
50
|
+
@root_span = nil
|
|
51
|
+
@root_context = nil
|
|
52
|
+
@tool_spans = {} # tool_use_id => span
|
|
53
|
+
@first_user_input = nil # capture first user prompt for trace input
|
|
54
|
+
@last_assistant_text = nil # capture last assistant text for trace output
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def on_user_prompt(prompt)
|
|
58
|
+
return if @first_user_input # only capture the first prompt
|
|
59
|
+
|
|
60
|
+
@first_user_input = prompt.to_s
|
|
61
|
+
# If root span already exists, set immediately; otherwise start_trace will apply it
|
|
62
|
+
@root_span&.set_attribute('input.value', truncate(@first_user_input)) unless @first_user_input.empty?
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def on_message(message)
|
|
66
|
+
case message
|
|
67
|
+
when ClaudeAgentSDK::InitMessage
|
|
68
|
+
start_trace(message)
|
|
69
|
+
when ClaudeAgentSDK::AssistantMessage
|
|
70
|
+
handle_assistant(message)
|
|
71
|
+
when ClaudeAgentSDK::UserMessage
|
|
72
|
+
handle_user(message)
|
|
73
|
+
when ClaudeAgentSDK::ResultMessage
|
|
74
|
+
end_trace(message)
|
|
75
|
+
when ClaudeAgentSDK::APIRetryMessage
|
|
76
|
+
record_retry_event(message)
|
|
77
|
+
when ClaudeAgentSDK::RateLimitEvent
|
|
78
|
+
record_rate_limit_event(message)
|
|
79
|
+
when ClaudeAgentSDK::ToolProgressMessage
|
|
80
|
+
record_tool_progress_event(message)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def on_error(error)
|
|
85
|
+
return unless @root_span
|
|
86
|
+
|
|
87
|
+
@root_span.record_exception(error)
|
|
88
|
+
@root_span.status = OpenTelemetry::Trace::Status.error(error.message)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def on_close
|
|
92
|
+
@tool_spans.each_value(&:finish)
|
|
93
|
+
@tool_spans.clear
|
|
94
|
+
@root_span&.finish
|
|
95
|
+
@root_span = nil
|
|
96
|
+
@root_context = nil
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def start_trace(message)
|
|
102
|
+
attrs = {
|
|
103
|
+
# gen_ai semantic conventions (recognized by Langfuse, Datadog, etc.)
|
|
104
|
+
'gen_ai.system' => 'anthropic',
|
|
105
|
+
'gen_ai.request.model' => message.model,
|
|
106
|
+
# OpenInference conventions (recognized by Langfuse, Arize)
|
|
107
|
+
'openinference.span.kind' => 'AGENT',
|
|
108
|
+
'llm.model_name' => message.model,
|
|
109
|
+
'input.mime_type' => 'text/plain',
|
|
110
|
+
'output.mime_type' => 'text/plain',
|
|
111
|
+
# Langfuse: 'agent' type triggers the trace flow diagram (DAG graph)
|
|
112
|
+
'langfuse.observation.type' => 'agent',
|
|
113
|
+
# Session tracking
|
|
114
|
+
'session.id' => message.session_id
|
|
115
|
+
}.merge(@default_attributes)
|
|
116
|
+
|
|
117
|
+
attrs['claude_code.version'] = message.claude_code_version if message.respond_to?(:claude_code_version) && message.claude_code_version
|
|
118
|
+
attrs['claude_code.cwd'] = message.cwd if message.respond_to?(:cwd) && message.cwd
|
|
119
|
+
attrs['claude_code.permission_mode'] = message.permission_mode if message.respond_to?(:permission_mode) && message.permission_mode
|
|
120
|
+
|
|
121
|
+
@root_span = @tracer.start_span('claude_agent.session', attributes: compact_attrs(attrs))
|
|
122
|
+
@root_context = OpenTelemetry::Trace.context_with_span(@root_span)
|
|
123
|
+
|
|
124
|
+
# Apply buffered prompt if on_user_prompt was called before InitMessage arrived
|
|
125
|
+
@root_span.set_attribute('input.value', truncate(@first_user_input)) if @first_user_input && !@first_user_input.empty?
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def handle_assistant(message)
|
|
129
|
+
return unless @root_context
|
|
130
|
+
|
|
131
|
+
# Extract text content for gen_ai.completion
|
|
132
|
+
text_parts = []
|
|
133
|
+
tool_use_blocks = []
|
|
134
|
+
|
|
135
|
+
(message.content || []).each do |block|
|
|
136
|
+
case block
|
|
137
|
+
when ClaudeAgentSDK::TextBlock
|
|
138
|
+
text_parts << block.text
|
|
139
|
+
when ClaudeAgentSDK::ToolUseBlock
|
|
140
|
+
tool_use_blocks << block
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Track last assistant text for trace output
|
|
145
|
+
combined_text = text_parts.join("\n")
|
|
146
|
+
@last_assistant_text = combined_text unless combined_text.empty?
|
|
147
|
+
|
|
148
|
+
# Create generation span
|
|
149
|
+
usage = message.usage || {}
|
|
150
|
+
input_tokens = usage[:input_tokens] || usage['input_tokens']
|
|
151
|
+
output_tokens = usage[:output_tokens] || usage['output_tokens']
|
|
152
|
+
attrs = {
|
|
153
|
+
'openinference.span.kind' => 'LLM',
|
|
154
|
+
'langfuse.observation.type' => 'generation',
|
|
155
|
+
'gen_ai.response.model' => message.model,
|
|
156
|
+
'llm.model_name' => message.model,
|
|
157
|
+
'gen_ai.usage.input_tokens' => input_tokens,
|
|
158
|
+
'gen_ai.usage.output_tokens' => output_tokens,
|
|
159
|
+
'gen_ai.completion' => truncate(combined_text),
|
|
160
|
+
# OpenInference: Langfuse maps output.value to the Preview Output field
|
|
161
|
+
'output.value' => truncate(combined_text)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
OpenTelemetry::Context.with_current(@root_context) do
|
|
165
|
+
span = @tracer.start_span('claude_agent.generation', attributes: compact_attrs(attrs))
|
|
166
|
+
span.finish
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Start tool spans for any ToolUseBlocks
|
|
170
|
+
tool_use_blocks.each { |block| start_tool_span(block) }
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def handle_user(message)
|
|
174
|
+
return unless @root_context
|
|
175
|
+
|
|
176
|
+
content = message.content
|
|
177
|
+
return unless content.is_a?(Array)
|
|
178
|
+
|
|
179
|
+
content.each do |block|
|
|
180
|
+
case block
|
|
181
|
+
when ClaudeAgentSDK::ToolResultBlock
|
|
182
|
+
end_tool_span(block)
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def end_trace(message)
|
|
188
|
+
return unless @root_span
|
|
189
|
+
|
|
190
|
+
usage = message.usage || {}
|
|
191
|
+
input_tokens = usage[:input_tokens] || usage['input_tokens']
|
|
192
|
+
output_tokens = usage[:output_tokens] || usage['output_tokens']
|
|
193
|
+
total_tokens = (input_tokens || 0) + (output_tokens || 0) if input_tokens || output_tokens
|
|
194
|
+
|
|
195
|
+
# Set trace output (last assistant response — shown in Langfuse UI)
|
|
196
|
+
# ResultMessage.result has the final text; fall back to last tracked assistant text
|
|
197
|
+
trace_output = message.result || @last_assistant_text
|
|
198
|
+
|
|
199
|
+
attrs = {
|
|
200
|
+
# gen_ai conventions
|
|
201
|
+
'gen_ai.usage.cost' => message.total_cost_usd,
|
|
202
|
+
'gen_ai.usage.input_tokens' => input_tokens,
|
|
203
|
+
'gen_ai.usage.output_tokens' => output_tokens,
|
|
204
|
+
# OpenInference conventions (Langfuse maps these to usage/cost)
|
|
205
|
+
'llm.token_count.prompt' => input_tokens,
|
|
206
|
+
'llm.token_count.completion' => output_tokens,
|
|
207
|
+
'llm.token_count.total' => total_tokens,
|
|
208
|
+
'llm.cost.total' => message.total_cost_usd,
|
|
209
|
+
# Trace output (Langfuse shows this in the trace detail view)
|
|
210
|
+
'output.value' => truncate(trace_output),
|
|
211
|
+
# Session metadata
|
|
212
|
+
'claude_agent.duration_ms' => message.duration_ms,
|
|
213
|
+
'claude_agent.duration_api_ms' => message.duration_api_ms,
|
|
214
|
+
'claude_agent.num_turns' => message.num_turns,
|
|
215
|
+
'claude_agent.stop_reason' => message.stop_reason
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
@root_span.status = OpenTelemetry::Trace::Status.error(message.stop_reason || 'error') if message.is_error
|
|
219
|
+
|
|
220
|
+
@root_span.add_attributes(compact_attrs(attrs))
|
|
221
|
+
@root_span.finish
|
|
222
|
+
@root_span = nil
|
|
223
|
+
@root_context = nil
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def start_tool_span(block)
|
|
227
|
+
return unless @root_context
|
|
228
|
+
|
|
229
|
+
input_json = truncate(safe_json(block.input))
|
|
230
|
+
attrs = {
|
|
231
|
+
'openinference.span.kind' => 'TOOL',
|
|
232
|
+
'langfuse.observation.type' => 'tool',
|
|
233
|
+
'tool.name' => block.name,
|
|
234
|
+
# OpenInference: Langfuse maps these to the Preview Input/Output fields
|
|
235
|
+
'input.value' => input_json,
|
|
236
|
+
'input.mime_type' => 'application/json'
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
OpenTelemetry::Context.with_current(@root_context) do
|
|
240
|
+
span = @tracer.start_span("claude_agent.tool.#{block.name}", attributes: compact_attrs(attrs))
|
|
241
|
+
@tool_spans[block.id] = span
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def end_tool_span(block)
|
|
246
|
+
span = @tool_spans.delete(block.tool_use_id)
|
|
247
|
+
return unless span
|
|
248
|
+
|
|
249
|
+
# OpenInference: Langfuse maps output.value to the Preview Output field
|
|
250
|
+
span.set_attribute('output.value', truncate(block.content.to_s))
|
|
251
|
+
span.status = OpenTelemetry::Trace::Status.error('tool error') if block.is_error
|
|
252
|
+
span.finish
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def record_retry_event(message)
|
|
256
|
+
return unless @root_span
|
|
257
|
+
|
|
258
|
+
@root_span.add_event('api_retry', attributes: compact_attrs(
|
|
259
|
+
'attempt' => message.attempt,
|
|
260
|
+
'max_retries' => message.max_retries,
|
|
261
|
+
'retry_delay_ms' => message.retry_delay_ms,
|
|
262
|
+
'error_status' => message.error_status,
|
|
263
|
+
'error' => message.error
|
|
264
|
+
))
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def record_rate_limit_event(message)
|
|
268
|
+
return unless @root_span
|
|
269
|
+
|
|
270
|
+
info = message.rate_limit_info
|
|
271
|
+
attrs = {}
|
|
272
|
+
if info
|
|
273
|
+
attrs['status'] = info.status if info.respond_to?(:status)
|
|
274
|
+
attrs['rate_limit_type'] = info.rate_limit_type if info.respond_to?(:rate_limit_type)
|
|
275
|
+
end
|
|
276
|
+
@root_span.add_event('rate_limit', attributes: compact_attrs(attrs))
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def record_tool_progress_event(message)
|
|
280
|
+
return unless @root_span
|
|
281
|
+
|
|
282
|
+
@root_span.add_event('tool_progress', attributes: compact_attrs(
|
|
283
|
+
'tool_name' => message.tool_name,
|
|
284
|
+
'tool_use_id' => message.tool_use_id,
|
|
285
|
+
'elapsed_time_seconds' => message.elapsed_time_seconds
|
|
286
|
+
))
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# Remove nil values from attributes hash (OTel rejects nil attribute values)
|
|
290
|
+
def compact_attrs(attrs)
|
|
291
|
+
attrs.compact
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
def truncate(str)
|
|
295
|
+
return nil unless str
|
|
296
|
+
|
|
297
|
+
str.length > MAX_ATTRIBUTE_LENGTH ? str[0...MAX_ATTRIBUTE_LENGTH] : str
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def safe_json(obj)
|
|
301
|
+
JSON.generate(obj)
|
|
302
|
+
rescue StandardError
|
|
303
|
+
obj.to_s
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ClaudeAgentSDK
|
|
4
|
+
# Base module for message observers.
|
|
5
|
+
#
|
|
6
|
+
# Include this module and override the methods you care about.
|
|
7
|
+
# All methods have no-op defaults so observers only need to implement
|
|
8
|
+
# the callbacks relevant to their use case.
|
|
9
|
+
#
|
|
10
|
+
# Observers are registered via ClaudeAgentOptions#observers and are called
|
|
11
|
+
# for every parsed message in both query() and Client#receive_messages.
|
|
12
|
+
# Observer errors are rescued so they never crash the main message pipeline.
|
|
13
|
+
#
|
|
14
|
+
# @example Custom logging observer
|
|
15
|
+
# class LoggingObserver
|
|
16
|
+
# include ClaudeAgentSDK::Observer
|
|
17
|
+
#
|
|
18
|
+
# def on_message(message)
|
|
19
|
+
# puts "[#{message.class.name}] received"
|
|
20
|
+
# end
|
|
21
|
+
# end
|
|
22
|
+
module Observer
|
|
23
|
+
# Called with the user's prompt text (not echoed back by CLI in streaming mode).
|
|
24
|
+
# @param prompt [String] The user's prompt string
|
|
25
|
+
def on_user_prompt(prompt); end
|
|
26
|
+
|
|
27
|
+
# Called for every parsed message (typed object from MessageParser).
|
|
28
|
+
# @param message [Object] A typed message (AssistantMessage, ResultMessage, etc.)
|
|
29
|
+
def on_message(message); end
|
|
30
|
+
|
|
31
|
+
# Called when a transport or parse error occurs.
|
|
32
|
+
# @param error [Exception] The error that occurred
|
|
33
|
+
def on_error(error); end
|
|
34
|
+
|
|
35
|
+
# Called when the query or client disconnects. Use this to flush buffers.
|
|
36
|
+
def on_close; end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -1720,7 +1720,7 @@ module ClaudeAgentSDK
|
|
|
1720
1720
|
:output_format, :max_budget_usd, :max_thinking_tokens,
|
|
1721
1721
|
:fallback_model, :plugins, :debug_stderr,
|
|
1722
1722
|
:betas, :tools, :sandbox, :enable_file_checkpointing, :append_allowed_tools,
|
|
1723
|
-
:thinking, :effort, :bare
|
|
1723
|
+
:thinking, :effort, :bare, :observers
|
|
1724
1724
|
|
|
1725
1725
|
# Non-nil defaults for options that need them.
|
|
1726
1726
|
# Keys absent from here default to nil.
|
|
@@ -1728,7 +1728,8 @@ module ClaudeAgentSDK
|
|
|
1728
1728
|
allowed_tools: [], disallowed_tools: [], add_dirs: [],
|
|
1729
1729
|
mcp_servers: {}, env: {}, extra_args: {},
|
|
1730
1730
|
continue_conversation: false, include_partial_messages: false,
|
|
1731
|
-
fork_session: false, enable_file_checkpointing: false
|
|
1731
|
+
fork_session: false, enable_file_checkpointing: false,
|
|
1732
|
+
observers: []
|
|
1732
1733
|
}.freeze
|
|
1733
1734
|
|
|
1734
1735
|
# Valid option names derived from attr_accessor declarations.
|
data/lib/claude_agent_sdk.rb
CHANGED
|
@@ -4,6 +4,7 @@ require_relative 'claude_agent_sdk/version'
|
|
|
4
4
|
require_relative 'claude_agent_sdk/errors'
|
|
5
5
|
require_relative 'claude_agent_sdk/configuration'
|
|
6
6
|
require_relative 'claude_agent_sdk/types'
|
|
7
|
+
require_relative 'claude_agent_sdk/observer'
|
|
7
8
|
require_relative 'claude_agent_sdk/transport'
|
|
8
9
|
require_relative 'claude_agent_sdk/subprocess_cli_transport'
|
|
9
10
|
require_relative 'claude_agent_sdk/message_parser'
|
|
@@ -17,6 +18,24 @@ require 'securerandom'
|
|
|
17
18
|
|
|
18
19
|
# Claude Agent SDK for Ruby
|
|
19
20
|
module ClaudeAgentSDK
|
|
21
|
+
# Resolve observers array: callables (Proc/lambda) are invoked to produce
|
|
22
|
+
# a fresh instance per query/session (thread-safe); plain objects are used as-is.
|
|
23
|
+
# Array() guards against nil (e.g., when observers: nil is passed explicitly).
|
|
24
|
+
def self.resolve_observers(observers)
|
|
25
|
+
Array(observers).map do |obs|
|
|
26
|
+
obs.respond_to?(:call) ? obs.call : obs
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Safely call a method on each observer, suppressing any errors.
|
|
31
|
+
def self.notify_observers(observers, method, *args)
|
|
32
|
+
observers.each do |obs|
|
|
33
|
+
obs.send(method, *args)
|
|
34
|
+
rescue StandardError
|
|
35
|
+
nil
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
20
39
|
# Look up a value in a hash that may use symbol or string keys in camelCase or snake_case.
|
|
21
40
|
# Returns the first non-nil value found, preserving false as a meaningful value.
|
|
22
41
|
def self.flexible_fetch(hash, camel_key, snake_key)
|
|
@@ -120,6 +139,9 @@ module ClaudeAgentSDK
|
|
|
120
139
|
configured_options = options.dup_with(permission_prompt_tool_name: 'stdio')
|
|
121
140
|
end
|
|
122
141
|
|
|
142
|
+
# Resolve callable observers into fresh instances (thread-safe for global defaults)
|
|
143
|
+
resolved_observers = ClaudeAgentSDK.resolve_observers(configured_options.observers)
|
|
144
|
+
|
|
123
145
|
Async do
|
|
124
146
|
# Always use streaming mode with control protocol (matches Python SDK).
|
|
125
147
|
# This sends agents via initialize request instead of CLI args,
|
|
@@ -174,6 +196,7 @@ module ClaudeAgentSDK
|
|
|
174
196
|
|
|
175
197
|
# Send prompt(s) as user messages, then close stdin
|
|
176
198
|
if prompt.is_a?(String)
|
|
199
|
+
ClaudeAgentSDK.notify_observers(resolved_observers, :on_user_prompt, prompt)
|
|
177
200
|
message = {
|
|
178
201
|
type: 'user',
|
|
179
202
|
message: { role: 'user', content: prompt },
|
|
@@ -191,9 +214,13 @@ module ClaudeAgentSDK
|
|
|
191
214
|
# Read and yield messages from the query handler (filters out control messages)
|
|
192
215
|
query_handler.receive_messages do |data|
|
|
193
216
|
message = MessageParser.parse(data)
|
|
194
|
-
|
|
217
|
+
if message
|
|
218
|
+
ClaudeAgentSDK.notify_observers(resolved_observers, :on_message, message)
|
|
219
|
+
block.call(message)
|
|
220
|
+
end
|
|
195
221
|
end
|
|
196
222
|
ensure
|
|
223
|
+
ClaudeAgentSDK.notify_observers(resolved_observers, :on_close)
|
|
197
224
|
# query_handler.close stops the background read task and closes the transport
|
|
198
225
|
if query_handler
|
|
199
226
|
query_handler.close
|
|
@@ -308,6 +335,9 @@ module ClaudeAgentSDK
|
|
|
308
335
|
@query_handler.start
|
|
309
336
|
@query_handler.initialize_protocol
|
|
310
337
|
|
|
338
|
+
# Resolve callable observers into fresh instances (thread-safe for global defaults)
|
|
339
|
+
@resolved_observers = ClaudeAgentSDK.resolve_observers(@options.observers)
|
|
340
|
+
|
|
311
341
|
@connected = true
|
|
312
342
|
|
|
313
343
|
# Optionally send initial prompt/messages after connection is ready.
|
|
@@ -331,6 +361,7 @@ module ClaudeAgentSDK
|
|
|
331
361
|
def query(prompt, session_id: 'default')
|
|
332
362
|
raise CLIConnectionError, 'Not connected. Call connect() first' unless @connected
|
|
333
363
|
|
|
364
|
+
ClaudeAgentSDK.notify_observers(@resolved_observers, :on_user_prompt, prompt)
|
|
334
365
|
message = {
|
|
335
366
|
type: 'user',
|
|
336
367
|
message: { role: 'user', content: prompt },
|
|
@@ -349,7 +380,10 @@ module ClaudeAgentSDK
|
|
|
349
380
|
|
|
350
381
|
@query_handler.receive_messages do |data|
|
|
351
382
|
message = MessageParser.parse(data)
|
|
352
|
-
|
|
383
|
+
if message
|
|
384
|
+
ClaudeAgentSDK.notify_observers(@resolved_observers, :on_message, message)
|
|
385
|
+
block.call(message)
|
|
386
|
+
end
|
|
353
387
|
end
|
|
354
388
|
end
|
|
355
389
|
|
|
@@ -439,6 +473,7 @@ module ClaudeAgentSDK
|
|
|
439
473
|
def disconnect
|
|
440
474
|
return unless @connected
|
|
441
475
|
|
|
476
|
+
ClaudeAgentSDK.notify_observers(@resolved_observers || [], :on_close)
|
|
442
477
|
@query_handler&.close
|
|
443
478
|
@query_handler = nil
|
|
444
479
|
@transport = nil
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: claude-agent-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Community Contributors
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-04-
|
|
10
|
+
date: 2026-04-03 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: async
|
|
@@ -106,7 +106,10 @@ files:
|
|
|
106
106
|
- lib/claude_agent_sdk.rb
|
|
107
107
|
- lib/claude_agent_sdk/configuration.rb
|
|
108
108
|
- lib/claude_agent_sdk/errors.rb
|
|
109
|
+
- lib/claude_agent_sdk/instrumentation.rb
|
|
110
|
+
- lib/claude_agent_sdk/instrumentation/otel.rb
|
|
109
111
|
- lib/claude_agent_sdk/message_parser.rb
|
|
112
|
+
- lib/claude_agent_sdk/observer.rb
|
|
110
113
|
- lib/claude_agent_sdk/query.rb
|
|
111
114
|
- lib/claude_agent_sdk/sdk_mcp_server.rb
|
|
112
115
|
- lib/claude_agent_sdk/session_mutations.rb
|