@introspection-sdk/introspection-node 0.20.0 → 0.21.1

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.
Files changed (211) hide show
  1. package/README.md +28 -8
  2. package/dist/auth.d.ts +1 -1
  3. package/dist/client.d.ts +4 -3
  4. package/dist/client.d.ts.map +1 -1
  5. package/dist/client.js +16 -7
  6. package/dist/client.js.map +1 -1
  7. package/dist/dev-target.js +1 -1
  8. package/dist/index.d.ts +3 -7
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +2 -6
  11. package/dist/index.js.map +1 -1
  12. package/dist/otel/index.d.ts +1 -14
  13. package/dist/otel/index.d.ts.map +1 -1
  14. package/dist/otel/index.js +10 -18
  15. package/dist/otel/index.js.map +1 -1
  16. package/dist/otel/init.d.ts +19 -21
  17. package/dist/otel/init.d.ts.map +1 -1
  18. package/dist/otel/init.js +62 -48
  19. package/dist/otel/init.js.map +1 -1
  20. package/dist/otel/integrations/base.d.ts +17 -37
  21. package/dist/otel/integrations/base.d.ts.map +1 -1
  22. package/dist/otel/integrations/base.js +6 -13
  23. package/dist/otel/integrations/base.js.map +1 -1
  24. package/dist/otel/integrations/index.d.ts +3 -4
  25. package/dist/otel/integrations/index.d.ts.map +1 -1
  26. package/dist/otel/integrations/index.js +6 -25
  27. package/dist/otel/integrations/index.js.map +1 -1
  28. package/dist/otel/integrations/pi.d.ts.map +1 -1
  29. package/dist/otel/integrations/pi.js +9 -1
  30. package/dist/otel/integrations/pi.js.map +1 -1
  31. package/dist/otel/logs.d.ts +41 -8
  32. package/dist/otel/logs.d.ts.map +1 -1
  33. package/dist/otel/logs.js +95 -66
  34. package/dist/otel/logs.js.map +1 -1
  35. package/dist/otel/pi.d.ts +18 -5
  36. package/dist/otel/pi.d.ts.map +1 -1
  37. package/dist/otel/pi.js +38 -9
  38. package/dist/otel/pi.js.map +1 -1
  39. package/dist/otel/register.d.ts +2 -2
  40. package/dist/otel/register.d.ts.map +1 -1
  41. package/dist/otel/register.js +7 -9
  42. package/dist/otel/register.js.map +1 -1
  43. package/dist/otel/setup.d.ts +2 -2
  44. package/dist/otel/setup.d.ts.map +1 -1
  45. package/dist/otel/setup.js +16 -6
  46. package/dist/otel/setup.js.map +1 -1
  47. package/dist/otel/span-processor.d.ts +18 -5
  48. package/dist/otel/span-processor.d.ts.map +1 -1
  49. package/dist/otel/span-processor.js +50 -65
  50. package/dist/otel/span-processor.js.map +1 -1
  51. package/dist/resources/experiments.d.ts +10 -5
  52. package/dist/resources/experiments.d.ts.map +1 -1
  53. package/dist/resources/experiments.js +9 -25
  54. package/dist/resources/experiments.js.map +1 -1
  55. package/dist/resources/recipes.d.ts +6 -6
  56. package/dist/resources/recipes.d.ts.map +1 -1
  57. package/dist/resources/recipes.js +5 -23
  58. package/dist/resources/recipes.js.map +1 -1
  59. package/dist/resources/runtimes.d.ts +2 -2
  60. package/dist/resources/runtimes.d.ts.map +1 -1
  61. package/dist/resources/runtimes.js +2 -2
  62. package/dist/resources/runtimes.js.map +1 -1
  63. package/dist/runner.d.ts +8 -4
  64. package/dist/runner.d.ts.map +1 -1
  65. package/dist/runner.js +26 -18
  66. package/dist/runner.js.map +1 -1
  67. package/dist/types.d.ts +1 -1
  68. package/dist/types.d.ts.map +1 -1
  69. package/dist/types.js +1 -1
  70. package/dist/types.js.map +1 -1
  71. package/dist/utils.d.ts +45 -0
  72. package/dist/utils.d.ts.map +1 -1
  73. package/dist/utils.js +61 -2
  74. package/dist/utils.js.map +1 -1
  75. package/dist/version.d.ts +1 -1
  76. package/dist/version.d.ts.map +1 -1
  77. package/dist/version.js +1 -1
  78. package/dist/version.js.map +1 -1
  79. package/package.json +16 -58
  80. package/.turbo/turbo-build.log +0 -4
  81. package/dist/converters/claude.d.ts +0 -150
  82. package/dist/converters/claude.d.ts.map +0 -1
  83. package/dist/converters/claude.js +0 -211
  84. package/dist/converters/claude.js.map +0 -1
  85. package/dist/converters/gemini.d.ts +0 -90
  86. package/dist/converters/gemini.d.ts.map +0 -1
  87. package/dist/converters/gemini.js +0 -171
  88. package/dist/converters/gemini.js.map +0 -1
  89. package/dist/converters/index.d.ts +0 -9
  90. package/dist/converters/index.d.ts.map +0 -1
  91. package/dist/converters/index.js +0 -9
  92. package/dist/converters/index.js.map +0 -1
  93. package/dist/converters/openai.d.ts +0 -51
  94. package/dist/converters/openai.d.ts.map +0 -1
  95. package/dist/converters/openai.js +0 -269
  96. package/dist/converters/openai.js.map +0 -1
  97. package/dist/converters/openinference.d.ts +0 -107
  98. package/dist/converters/openinference.d.ts.map +0 -1
  99. package/dist/converters/openinference.js +0 -553
  100. package/dist/converters/openinference.js.map +0 -1
  101. package/dist/converters/vercel.d.ts +0 -21
  102. package/dist/converters/vercel.d.ts.map +0 -1
  103. package/dist/converters/vercel.js +0 -236
  104. package/dist/converters/vercel.js.map +0 -1
  105. package/dist/otel/anthropic.d.ts +0 -63
  106. package/dist/otel/anthropic.d.ts.map +0 -1
  107. package/dist/otel/anthropic.js +0 -475
  108. package/dist/otel/anthropic.js.map +0 -1
  109. package/dist/otel/claude-hooks.d.ts +0 -346
  110. package/dist/otel/claude-hooks.d.ts.map +0 -1
  111. package/dist/otel/claude-hooks.js +0 -647
  112. package/dist/otel/claude-hooks.js.map +0 -1
  113. package/dist/otel/claude-wrapper.d.ts +0 -93
  114. package/dist/otel/claude-wrapper.d.ts.map +0 -1
  115. package/dist/otel/claude-wrapper.js +0 -155
  116. package/dist/otel/claude-wrapper.js.map +0 -1
  117. package/dist/otel/gemini.d.ts +0 -56
  118. package/dist/otel/gemini.d.ts.map +0 -1
  119. package/dist/otel/gemini.js +0 -350
  120. package/dist/otel/gemini.js.map +0 -1
  121. package/dist/otel/integrations/anthropic.d.ts +0 -10
  122. package/dist/otel/integrations/anthropic.d.ts.map +0 -1
  123. package/dist/otel/integrations/anthropic.js +0 -26
  124. package/dist/otel/integrations/anthropic.js.map +0 -1
  125. package/dist/otel/integrations/claude-agent.d.ts +0 -12
  126. package/dist/otel/integrations/claude-agent.d.ts.map +0 -1
  127. package/dist/otel/integrations/claude-agent.js +0 -28
  128. package/dist/otel/integrations/claude-agent.js.map +0 -1
  129. package/dist/otel/integrations/gemini.d.ts +0 -11
  130. package/dist/otel/integrations/gemini.d.ts.map +0 -1
  131. package/dist/otel/integrations/gemini.js +0 -23
  132. package/dist/otel/integrations/gemini.js.map +0 -1
  133. package/dist/otel/integrations/langchain.d.ts +0 -15
  134. package/dist/otel/integrations/langchain.d.ts.map +0 -1
  135. package/dist/otel/integrations/langchain.js +0 -43
  136. package/dist/otel/integrations/langchain.js.map +0 -1
  137. package/dist/otel/integrations/mastra.d.ts +0 -14
  138. package/dist/otel/integrations/mastra.d.ts.map +0 -1
  139. package/dist/otel/integrations/mastra.js +0 -31
  140. package/dist/otel/integrations/mastra.js.map +0 -1
  141. package/dist/otel/integrations/openai-agents.d.ts +0 -11
  142. package/dist/otel/integrations/openai-agents.d.ts.map +0 -1
  143. package/dist/otel/integrations/openai-agents.js +0 -30
  144. package/dist/otel/integrations/openai-agents.js.map +0 -1
  145. package/dist/otel/integrations/vercel.d.ts +0 -14
  146. package/dist/otel/integrations/vercel.d.ts.map +0 -1
  147. package/dist/otel/integrations/vercel.js +0 -20
  148. package/dist/otel/integrations/vercel.js.map +0 -1
  149. package/dist/otel/langchain-handler.d.ts +0 -112
  150. package/dist/otel/langchain-handler.d.ts.map +0 -1
  151. package/dist/otel/langchain-handler.js +0 -561
  152. package/dist/otel/langchain-handler.js.map +0 -1
  153. package/dist/otel/mastra-exporter.d.ts +0 -87
  154. package/dist/otel/mastra-exporter.d.ts.map +0 -1
  155. package/dist/otel/mastra-exporter.js +0 -530
  156. package/dist/otel/mastra-exporter.js.map +0 -1
  157. package/dist/otel/openai.d.ts +0 -20
  158. package/dist/otel/openai.d.ts.map +0 -1
  159. package/dist/otel/openai.js +0 -55
  160. package/dist/otel/openai.js.map +0 -1
  161. package/dist/otel/tracing-processor.d.ts +0 -139
  162. package/dist/otel/tracing-processor.d.ts.map +0 -1
  163. package/dist/otel/tracing-processor.js +0 -434
  164. package/dist/otel/tracing-processor.js.map +0 -1
  165. package/eslint.config.mjs +0 -4
  166. package/src/auth.ts +0 -236
  167. package/src/client.ts +0 -163
  168. package/src/converters/claude.ts +0 -343
  169. package/src/converters/gemini.ts +0 -243
  170. package/src/converters/index.ts +0 -50
  171. package/src/converters/openai.ts +0 -300
  172. package/src/converters/openinference.ts +0 -665
  173. package/src/converters/vercel.ts +0 -302
  174. package/src/dev-target.ts +0 -79
  175. package/src/http.ts +0 -43
  176. package/src/index.ts +0 -277
  177. package/src/otel/anthropic.ts +0 -619
  178. package/src/otel/claude-hooks.ts +0 -1116
  179. package/src/otel/claude-wrapper.ts +0 -239
  180. package/src/otel/gemini.ts +0 -483
  181. package/src/otel/index.ts +0 -128
  182. package/src/otel/init.ts +0 -411
  183. package/src/otel/integrations/anthropic.ts +0 -36
  184. package/src/otel/integrations/base.ts +0 -158
  185. package/src/otel/integrations/claude-agent.ts +0 -36
  186. package/src/otel/integrations/gemini.ts +0 -30
  187. package/src/otel/integrations/index.ts +0 -152
  188. package/src/otel/integrations/langchain.ts +0 -60
  189. package/src/otel/integrations/mastra.ts +0 -48
  190. package/src/otel/integrations/openai-agents.ts +0 -47
  191. package/src/otel/integrations/pi.ts +0 -28
  192. package/src/otel/integrations/vercel.ts +0 -26
  193. package/src/otel/langchain-handler.ts +0 -804
  194. package/src/otel/logs.ts +0 -395
  195. package/src/otel/mastra-exporter.ts +0 -714
  196. package/src/otel/openai.ts +0 -93
  197. package/src/otel/pi.ts +0 -94
  198. package/src/otel/register.ts +0 -50
  199. package/src/otel/setup.ts +0 -215
  200. package/src/otel/span-processor.ts +0 -512
  201. package/src/otel/tracing-processor.ts +0 -637
  202. package/src/pagination.ts +0 -10
  203. package/src/proxy-from-env.d.ts +0 -7
  204. package/src/resources/experiments.ts +0 -176
  205. package/src/resources/recipes.ts +0 -74
  206. package/src/resources/runtimes.ts +0 -31
  207. package/src/runner.ts +0 -213
  208. package/src/types.ts +0 -13
  209. package/src/utils.ts +0 -95
  210. package/src/version.ts +0 -2
  211. package/tsconfig.json +0 -8
@@ -1,1116 +0,0 @@
1
- /**
2
- * Claude Agent SDK Instrumentation Hooks
3
- *
4
- * Provides OpenTelemetry instrumentation for the Claude Agent SDK via its hooks system.
5
- * Creates spans for sessions, tool uses, and subagent executions with gen_ai.* attributes.
6
- *
7
- * @see https://platform.claude.com/docs/en/agent-sdk/typescript
8
- */
9
-
10
- import {
11
- context as otelContext,
12
- propagation,
13
- trace as otelTrace,
14
- Span as OtelSpan,
15
- SpanKind,
16
- SpanStatusCode,
17
- type Attributes,
18
- } from "@opentelemetry/api";
19
- import {
20
- BasicTracerProvider,
21
- BatchSpanProcessor,
22
- SimpleSpanProcessor,
23
- type SpanExporter,
24
- type IdGenerator,
25
- } from "@opentelemetry/sdk-trace-base";
26
- import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
27
- import {
28
- resourceFromAttributes,
29
- defaultResource,
30
- } from "@opentelemetry/resources";
31
- import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
32
-
33
- import { logger, withOtlpHttpsProxy } from "../utils.js";
34
- import { VERSION } from "../version.js";
35
- import {
36
- convertClaudeSessionToOtelAttributes,
37
- type ClaudeContentBlock,
38
- } from "../converters/claude.js";
39
-
40
- // ---------------------------------------------------------------------------
41
- // Claude Agent SDK Hook types
42
- // Defined locally to avoid requiring the SDK as a compile-time dependency.
43
- // ---------------------------------------------------------------------------
44
-
45
- /** Common fields present on every hook input payload. */
46
- export interface BaseHookInput {
47
- /** Active session identifier. */
48
- session_id: string;
49
- /** Path to the session transcript file. */
50
- transcript_path: string;
51
- /** Working directory of the agent process. */
52
- cwd: string;
53
- /** Permission mode the session is running in. */
54
- permission_mode?: string;
55
- }
56
-
57
- /** Input payload for the `SessionStart` hook. */
58
- export interface SessionStartHookInput extends BaseHookInput {
59
- hook_event_name: "SessionStart";
60
- /** How the session was initiated. */
61
- source: "startup" | "resume" | "clear" | "compact";
62
- /** Agent type (e.g. `"main"`, `"subagent"`). */
63
- agent_type?: string;
64
- /** Model used for this session. */
65
- model?: string;
66
- }
67
-
68
- /** Input payload for the `SessionEnd` hook. */
69
- export interface SessionEndHookInput extends BaseHookInput {
70
- hook_event_name: "SessionEnd";
71
- /** Reason the session ended. */
72
- reason: string;
73
- }
74
-
75
- /** Input payload for the `PreToolUse` hook. */
76
- export interface PreToolUseHookInput extends BaseHookInput {
77
- hook_event_name: "PreToolUse";
78
- /** Name of the tool about to be invoked. */
79
- tool_name: string;
80
- /** Input data passed to the tool. */
81
- tool_input: unknown;
82
- /** Unique identifier for this tool invocation. */
83
- tool_use_id: string;
84
- }
85
-
86
- /** Input payload for the `PostToolUse` hook. */
87
- export interface PostToolUseHookInput extends BaseHookInput {
88
- hook_event_name: "PostToolUse";
89
- /** Name of the tool that was invoked. */
90
- tool_name: string;
91
- /** Input data that was passed to the tool. */
92
- tool_input: unknown;
93
- /** Response returned by the tool. */
94
- tool_response: unknown;
95
- /** Unique identifier for this tool invocation. */
96
- tool_use_id: string;
97
- }
98
-
99
- /** Input payload for the `SubagentStart` hook. */
100
- export interface SubagentStartHookInput extends BaseHookInput {
101
- hook_event_name: "SubagentStart";
102
- /** Identifier of the sub-agent. */
103
- agent_id: string;
104
- /** Type / name of the sub-agent. */
105
- agent_type: string;
106
- }
107
-
108
- /** Input payload for the `SubagentStop` hook. */
109
- export interface SubagentStopHookInput extends BaseHookInput {
110
- hook_event_name: "SubagentStop";
111
- /** Identifier of the sub-agent. */
112
- agent_id: string;
113
- /** Type / name of the sub-agent. */
114
- agent_type: string;
115
- /** Path to the sub-agent's transcript. */
116
- agent_transcript_path: string;
117
- }
118
-
119
- /** Discriminated union of all possible hook input payloads. */
120
- export type ClaudeHookInput =
121
- | SessionStartHookInput
122
- | SessionEndHookInput
123
- | PreToolUseHookInput
124
- | PostToolUseHookInput
125
- | SubagentStartHookInput
126
- | SubagentStopHookInput;
127
-
128
- /**
129
- * JSON-serialisable output returned by a hook callback
130
- * (`SyncHookJSONOutput` in the SDK).
131
- */
132
- export type ClaudeHookOutput = {
133
- /** Whether the SDK should continue processing. */
134
- continue?: boolean;
135
- /** Suppress the default output for this hook event. */
136
- suppressOutput?: boolean;
137
- /** Reason to stop the session. */
138
- stopReason?: string;
139
- /** Approval decision for `PreToolUse` hooks. */
140
- decision?: "approve" | "block";
141
- /** System message to inject into the conversation. */
142
- systemMessage?: string;
143
- /** Human-readable reason for the decision. */
144
- reason?: string;
145
- };
146
-
147
- /**
148
- * Async callback signature for a Claude Agent SDK hook.
149
- *
150
- * @param input - The hook input payload.
151
- * @param toolUseID - The tool-use ID (set for `PreToolUse` / `PostToolUse`).
152
- * @param options - Abort signal for cancellation.
153
- * @returns A {@link ClaudeHookOutput} controlling the SDK's behaviour.
154
- */
155
- export type ClaudeHookCallback = (
156
- input: ClaudeHookInput,
157
- toolUseID: string | undefined,
158
- options: { signal: AbortSignal },
159
- ) => Promise<ClaudeHookOutput>;
160
-
161
- /** Pairs an optional glob matcher with one or more hook callbacks. */
162
- export interface ClaudeHookCallbackMatcher {
163
- /** Glob pattern to match tool names (only relevant for tool hooks). */
164
- matcher?: string;
165
- /** Callbacks to invoke when the matcher (or all events) match. */
166
- hooks: ClaudeHookCallback[];
167
- /** Timeout in milliseconds for hook execution. */
168
- timeout?: number;
169
- }
170
-
171
- /** String literal union of all Claude Agent SDK hook event names. */
172
- export type ClaudeHookEvent =
173
- | "PreToolUse"
174
- | "PostToolUse"
175
- | "SessionStart"
176
- | "SessionEnd"
177
- | "SubagentStart"
178
- | "SubagentStop";
179
-
180
- /**
181
- * Full hooks configuration object accepted by the Claude Agent SDK
182
- * `query()` options. Maps each event name to an array of
183
- * {@link ClaudeHookCallbackMatcher} entries.
184
- */
185
- export type ClaudeHooksConfig = Partial<
186
- Record<ClaudeHookEvent, ClaudeHookCallbackMatcher[]>
187
- >;
188
-
189
- // ---------------------------------------------------------------------------
190
- // Claude Agent SDK message types (for extracting usage info)
191
- // ---------------------------------------------------------------------------
192
-
193
- /** Token-level usage counters reported by the Claude API. */
194
- export interface ClaudeUsage {
195
- /** Number of input tokens consumed. */
196
- input_tokens: number;
197
- /** Number of output tokens generated. */
198
- output_tokens: number;
199
- /** Tokens used to create new cache entries. */
200
- cache_creation_input_tokens?: number;
201
- /** Tokens read from cache. */
202
- cache_read_input_tokens?: number;
203
- }
204
-
205
- /** Per-model usage breakdown reported in a result message. */
206
- export interface ClaudeModelUsage {
207
- /** Input tokens for this model. */
208
- inputTokens: number;
209
- /** Output tokens for this model. */
210
- outputTokens: number;
211
- /** Tokens read from cache for this model. */
212
- cacheReadInputTokens: number;
213
- /** Tokens used to create cache for this model. */
214
- cacheCreationInputTokens: number;
215
- /** Estimated cost in USD for this model. */
216
- costUSD: number;
217
- }
218
-
219
- /** A `"result"` message yielded at the end of a `query()` stream. */
220
- export interface ClaudeResultMessage {
221
- type: "result";
222
- /** Aggregate token usage for the session. */
223
- usage?: ClaudeUsage;
224
- /** Total estimated cost in USD. */
225
- total_cost_usd?: number;
226
- /** Per-model usage breakdown. */
227
- modelUsage?: Record<string, ClaudeModelUsage>;
228
- /** Final text result (if any). */
229
- result?: string;
230
- /** Whether the session ended with an error. */
231
- is_error?: boolean;
232
- /** Session identifier. */
233
- session_id: string;
234
- }
235
-
236
- /** An `"assistant"` message yielded during a `query()` stream. */
237
- export interface ClaudeAssistantMessage {
238
- type: "assistant";
239
- /** The underlying API message with optional usage and content. */
240
- message?: {
241
- id?: string;
242
- model?: string;
243
- usage?: {
244
- input_tokens?: number;
245
- output_tokens?: number;
246
- cache_read_input_tokens?: number;
247
- cache_creation_input_tokens?: number;
248
- };
249
- content?: Array<{ type: string; text?: string }>;
250
- };
251
- /** Session identifier. */
252
- session_id: string;
253
- }
254
-
255
- /**
256
- * Discriminated union of all messages that can be yielded by a Claude Agent
257
- * SDK `query()` stream.
258
- */
259
- export type ClaudeSDKMessage =
260
- | ClaudeResultMessage
261
- | ClaudeAssistantMessage
262
- | { type: string; session_id?: string };
263
-
264
- /** Advanced options for testing and customization of {@link IntrospectionClaudeHooks}. */
265
- export interface ClaudeHooksAdvancedOptions {
266
- /** Custom span exporter (for testing) */
267
- spanExporter?: SpanExporter;
268
- /** Custom ID generator (for testing) */
269
- idGenerator?: IdGenerator;
270
- /** Use SimpleSpanProcessor instead of BatchSpanProcessor (for testing) */
271
- useSimpleSpanProcessor?: boolean;
272
- }
273
-
274
- /** Configuration for {@link IntrospectionClaudeHooks}. */
275
- export interface IntrospectionClaudeHooksOptions {
276
- /** Authentication token (env: INTROSPECTION_TOKEN) */
277
- token?: string;
278
- /** Base URL for the API (env: INTROSPECTION_BASE_OTEL_URL) */
279
- baseUrl?: string;
280
- /** Service name for the OTel resource (defaults to "claude-agent") */
281
- serviceName?: string;
282
- /** Agent name set on session spans as gen_ai.agent.name (defaults to serviceName) */
283
- agentName?: string;
284
- /** Agent ID set on session spans as gen_ai.agent.id */
285
- agentId?: string;
286
- /** Conversation ID set on session spans as gen_ai.conversation.id */
287
- conversationId?: string;
288
- /** Additional headers to include in requests */
289
- additionalHeaders?: Record<string, string>;
290
- /** Advanced options for testing and customization */
291
- advanced?: ClaudeHooksAdvancedOptions;
292
- }
293
-
294
- /**
295
- * Provides instrumented hooks for Claude Agent SDK.
296
- *
297
- * Creates OpenTelemetry spans with gen_ai.* semantic convention attributes:
298
- * - Session spans: gen_ai.provider.name, gen_ai.request.model
299
- * - Tool spans: gen_ai.tool.name, gen_ai.tool.call.arguments, gen_ai.tool.call.result
300
- * - Subagent spans: gen_ai.agent.name
301
- *
302
- * Usage tracking is captured from streamed messages via recordUsage().
303
- *
304
- * @example
305
- * ```typescript
306
- * import { query } from "@anthropic-ai/claude-agent-sdk";
307
- * import { IntrospectionClaudeHooks } from "@introspection-sdk/introspection-node";
308
- *
309
- * const hooks = new IntrospectionClaudeHooks();
310
- *
311
- * const stream = query({
312
- * prompt: "Hello",
313
- * options: {
314
- * hooks: hooks.getHooks(),
315
- * },
316
- * });
317
- *
318
- * for await (const message of stream) {
319
- * hooks.recordUsage(message);
320
- * // handle message...
321
- * }
322
- *
323
- * await hooks.shutdown();
324
- * ```
325
- */
326
- export class IntrospectionClaudeHooks {
327
- private _tracerProvider: BasicTracerProvider;
328
- private _tracer: ReturnType<BasicTracerProvider["getTracer"]>;
329
- private _agentName: string;
330
- private _agentId?: string;
331
- private _conversationId?: string;
332
- private _sessionSpans: Map<string, OtelSpan> = new Map();
333
- private _toolSpans: Map<string, OtelSpan> = new Map();
334
- private _subagentSpans: Map<string, OtelSpan> = new Map();
335
- private _sessionUsage: Map<
336
- string,
337
- {
338
- input: number;
339
- output: number;
340
- cacheRead: number;
341
- cacheCreation: number;
342
- cost?: number;
343
- model?: string;
344
- inputPrompt?: string;
345
- responseContent?: string;
346
- responseBlocks?: ClaudeContentBlock[];
347
- stopReason?: string;
348
- responseId?: string;
349
- toolNames?: string[];
350
- systemInstructions?: string;
351
- }
352
- > = new Map();
353
-
354
- constructor(options: IntrospectionClaudeHooksOptions = {}) {
355
- const advanced = options.advanced;
356
- const serviceName = options.serviceName || "claude-agent";
357
- this._agentName = options.agentName || serviceName;
358
- this._agentId = options.agentId;
359
- this._conversationId = options.conversationId;
360
- const resource = defaultResource().merge(
361
- resourceFromAttributes({ [ATTR_SERVICE_NAME]: serviceName }),
362
- );
363
-
364
- if (!advanced?.spanExporter) {
365
- const token = options.token || process.env.INTROSPECTION_TOKEN;
366
- if (!token) {
367
- throw new Error("INTROSPECTION_TOKEN is required");
368
- }
369
-
370
- const baseUrl =
371
- options.baseUrl ||
372
- process.env.INTROSPECTION_BASE_OTEL_URL ||
373
- "https://otel.introspection.dev";
374
-
375
- const headers: Record<string, string> = {
376
- "User-Agent": `introspection-sdk/${VERSION}`,
377
- Authorization: `Bearer ${token}`,
378
- ...options.additionalHeaders,
379
- };
380
-
381
- const endpoint = baseUrl.endsWith("/v1/traces")
382
- ? baseUrl
383
- : `${baseUrl.replace(/\/$/, "")}/v1/traces`;
384
-
385
- logger.info(
386
- `IntrospectionClaudeHooks initialized: service=${serviceName}, endpoint=${endpoint}`,
387
- );
388
-
389
- const spanExporter = new OTLPTraceExporter(
390
- withOtlpHttpsProxy({
391
- url: endpoint,
392
- headers,
393
- }),
394
- );
395
-
396
- // Default to sequential export for dev/staging tokens.
397
- const useSimple =
398
- advanced?.useSimpleSpanProcessor ||
399
- token.startsWith("intro_dev") ||
400
- token.startsWith("intro_staging");
401
- const spanProcessor = useSimple
402
- ? new SimpleSpanProcessor(spanExporter)
403
- : new BatchSpanProcessor(spanExporter, { scheduledDelayMillis: 1000 });
404
-
405
- this._tracerProvider = new BasicTracerProvider({
406
- resource,
407
- idGenerator: advanced?.idGenerator,
408
- spanProcessors: [spanProcessor],
409
- });
410
- } else {
411
- logger.info(
412
- `IntrospectionClaudeHooks initialized in test mode with custom exporter`,
413
- );
414
-
415
- const spanProcessor = advanced.useSimpleSpanProcessor
416
- ? new SimpleSpanProcessor(advanced.spanExporter)
417
- : new BatchSpanProcessor(advanced.spanExporter, {
418
- scheduledDelayMillis: 100,
419
- });
420
-
421
- this._tracerProvider = new BasicTracerProvider({
422
- resource,
423
- idGenerator: advanced.idGenerator,
424
- spanProcessors: [spanProcessor],
425
- });
426
- }
427
-
428
- this._tracer = this._tracerProvider.getTracer("claude-agent-sdk", VERSION);
429
- }
430
-
431
- /**
432
- * Build the hooks configuration object to pass to Claude Agent SDK
433
- * `query()` options.
434
- *
435
- * @returns A {@link ClaudeHooksConfig} wired to this instance's tracing.
436
- */
437
- getHooks(): ClaudeHooksConfig {
438
- return {
439
- SessionStart: [
440
- {
441
- hooks: [this._onSessionStart.bind(this)],
442
- },
443
- ],
444
- SessionEnd: [
445
- {
446
- hooks: [this._onSessionEnd.bind(this)],
447
- },
448
- ],
449
- PreToolUse: [
450
- {
451
- hooks: [this._onPreToolUse.bind(this)],
452
- },
453
- ],
454
- PostToolUse: [
455
- {
456
- hooks: [this._onPostToolUse.bind(this)],
457
- },
458
- ],
459
- SubagentStart: [
460
- {
461
- hooks: [this._onSubagentStart.bind(this)],
462
- },
463
- ],
464
- SubagentStop: [
465
- {
466
- hooks: [this._onSubagentStop.bind(this)],
467
- },
468
- ],
469
- };
470
- }
471
-
472
- /**
473
- * Resolve effective agent / conversation identity for a session, in order
474
- * of precedence: OTel baggage on the active context > constructor option >
475
- * the Claude session ID. Lets callers override identity per-invocation via
476
- * `IntrospectionClient.withAgent()` / `.withConversation()` without
477
- * having to construct a new hooks instance per call.
478
- *
479
- * Requires an active context manager — see {@link setupTracing}.
480
- */
481
- private _resolveIdentity(sessionId: string): {
482
- agentName: string;
483
- agentId?: string;
484
- conversationId: string;
485
- } {
486
- const baggage = propagation.getBaggage(otelContext.active());
487
- const baggageAgentName = baggage?.getEntry("gen_ai.agent.name")?.value;
488
- const baggageAgentId = baggage?.getEntry("gen_ai.agent.id")?.value;
489
- const baggageConvId = baggage?.getEntry("gen_ai.conversation.id")?.value;
490
-
491
- return {
492
- agentName: baggageAgentName || this._agentName,
493
- agentId: baggageAgentId || this._agentId,
494
- conversationId: baggageConvId || this._conversationId || sessionId,
495
- };
496
- }
497
-
498
- /**
499
- * Set the input prompt for a session so it appears as
500
- * `gen_ai.input.messages` on the session span.
501
- *
502
- * If `sessionId` is not provided the prompt is buffered and associated
503
- * with the next session that starts via an `init` message.
504
- *
505
- * @param prompt - The user prompt string passed to `query()`.
506
- * @param sessionId - Optional session ID if already known.
507
- */
508
- setInputPrompt(prompt: string, sessionId?: string): void {
509
- if (sessionId) {
510
- const existing = this._sessionUsage.get(sessionId) || {
511
- input: 0,
512
- output: 0,
513
- cacheRead: 0,
514
- cacheCreation: 0,
515
- };
516
- existing.inputPrompt = prompt;
517
- this._sessionUsage.set(sessionId, existing);
518
- } else {
519
- // Store for when session starts
520
- this._pendingInputPrompt = prompt;
521
- }
522
- }
523
-
524
- private _pendingInputPrompt?: string;
525
-
526
- /**
527
- * Set system instructions for a session so they appear as
528
- * `gen_ai.system_instructions` on the session span.
529
- *
530
- * If `sessionId` is not provided the value is buffered and associated
531
- * with the next session that starts via an `init` message.
532
- *
533
- * @param instructions - The system prompt / instructions string.
534
- * @param sessionId - Optional session ID if already known.
535
- */
536
- setSystemInstructions(instructions: string, sessionId?: string): void {
537
- if (sessionId) {
538
- const existing = this._sessionUsage.get(sessionId) || {
539
- input: 0,
540
- output: 0,
541
- cacheRead: 0,
542
- cacheCreation: 0,
543
- };
544
- existing.systemInstructions = instructions;
545
- this._sessionUsage.set(sessionId, existing);
546
- } else {
547
- this._pendingSystemInstructions = instructions;
548
- }
549
- logger.info(
550
- `System instructions set (${instructions.length} chars)${sessionId ? ` for session ${sessionId}` : " (pending)"}`,
551
- );
552
- }
553
-
554
- private _pendingSystemInstructions?: string;
555
-
556
- /**
557
- * Record usage from a streamed SDK message.
558
- *
559
- * Call this for **every** message yielded by `query()` to capture token
560
- * usage. Session spans are automatically created on `init` messages and
561
- * ended on `result` messages.
562
- *
563
- * @param message - A {@link ClaudeSDKMessage} yielded by the `query()` stream.
564
- */
565
- recordUsage(message: ClaudeSDKMessage): void {
566
- const sessionId = message.session_id || "default";
567
- const msg = message as Record<string, unknown>;
568
-
569
- // Handle system init message - start session span
570
- if (message.type === "system" && msg.subtype === "init") {
571
- this._startSessionFromMessage(sessionId, msg);
572
- return;
573
- }
574
-
575
- if (message.type === "assistant") {
576
- const assistantMsg = message as ClaudeAssistantMessage;
577
- const existing = this._sessionUsage.get(sessionId) || {
578
- input: 0,
579
- output: 0,
580
- cacheRead: 0,
581
- cacheCreation: 0,
582
- };
583
-
584
- // Track usage
585
- const usage = assistantMsg.message?.usage;
586
- if (usage) {
587
- existing.input += usage.input_tokens || 0;
588
- existing.output += usage.output_tokens || 0;
589
- existing.cacheRead += usage.cache_read_input_tokens || 0;
590
- existing.cacheCreation += usage.cache_creation_input_tokens || 0;
591
- if (assistantMsg.message?.model) {
592
- existing.model = assistantMsg.message.model;
593
- }
594
- logger.debug(
595
- `Recorded usage for session ${sessionId}: +${usage.input_tokens || 0} input, +${usage.output_tokens || 0} output`,
596
- );
597
- }
598
-
599
- // Capture response ID from assistant message
600
- if (assistantMsg.message?.id) {
601
- existing.responseId = assistantMsg.message.id;
602
- }
603
-
604
- // Extract response content from assistant message
605
- const content = assistantMsg.message?.content;
606
- if (content && Array.isArray(content)) {
607
- // Store content blocks for proper conversion
608
- existing.responseBlocks = [
609
- ...(existing.responseBlocks || []),
610
- ...(content as ClaudeContentBlock[]),
611
- ];
612
- // Also extract text for backwards compatibility
613
- const textContent = content
614
- .filter((block) => block.type === "text" && block.text)
615
- .map((block) => block.text)
616
- .join("");
617
- if (textContent) {
618
- existing.responseContent =
619
- (existing.responseContent || "") + textContent;
620
- }
621
- }
622
-
623
- this._sessionUsage.set(sessionId, existing);
624
- } else if (message.type === "result") {
625
- const resultMsg = message as ClaudeResultMessage;
626
- const existing = this._sessionUsage.get(sessionId) || {
627
- input: 0,
628
- output: 0,
629
- cacheRead: 0,
630
- cacheCreation: 0,
631
- };
632
-
633
- if (resultMsg.usage) {
634
- // Result message has final totals
635
- existing.input = resultMsg.usage.input_tokens || existing.input;
636
- existing.output = resultMsg.usage.output_tokens || existing.output;
637
- if (resultMsg.usage.cache_read_input_tokens != null) {
638
- existing.cacheRead = resultMsg.usage.cache_read_input_tokens;
639
- }
640
- if (resultMsg.usage.cache_creation_input_tokens != null) {
641
- existing.cacheCreation = resultMsg.usage.cache_creation_input_tokens;
642
- }
643
- }
644
- // Fallback: aggregate cache tokens from per-model breakdown when top-level usage omits them
645
- if (
646
- resultMsg.modelUsage &&
647
- existing.cacheRead === 0 &&
648
- existing.cacheCreation === 0
649
- ) {
650
- for (const modelData of Object.values(resultMsg.modelUsage)) {
651
- existing.cacheRead += modelData.cacheReadInputTokens || 0;
652
- existing.cacheCreation += modelData.cacheCreationInputTokens || 0;
653
- }
654
- }
655
- if (resultMsg.total_cost_usd !== undefined) {
656
- existing.cost = resultMsg.total_cost_usd;
657
- }
658
-
659
- // Capture stop reason from result subtype
660
- const subtype = msg.subtype as string | undefined;
661
- if (subtype) {
662
- existing.stopReason = subtype;
663
- }
664
-
665
- this._sessionUsage.set(sessionId, existing);
666
- logger.debug(
667
- `Recorded final usage for session ${sessionId}: ${existing.input} input, ${existing.output} output, $${existing.cost || 0}`,
668
- );
669
-
670
- // End session span on result message
671
- this._endSessionFromMessage(sessionId, msg);
672
- }
673
- }
674
-
675
- /**
676
- * Start a session span from an init system message.
677
- * This provides an alternative to the SessionStart hook which has timing issues.
678
- */
679
- private _startSessionFromMessage(
680
- sessionId: string,
681
- msg: Record<string, unknown>,
682
- ): void {
683
- // Don't create duplicate spans if hook already created one
684
- if (this._sessionSpans.has(sessionId)) {
685
- logger.debug(`Session span already exists for ${sessionId}, skipping`);
686
- return;
687
- }
688
-
689
- logger.debug(`Starting session span from init message: ${sessionId}`);
690
- const ids = this._resolveIdentity(sessionId);
691
- const attrs: Attributes = {
692
- "gen_ai.provider.name": "anthropic",
693
- "gen_ai.operation.name": "chat",
694
- "gen_ai.agent.name": ids.agentName,
695
- "gen_ai.conversation.id": ids.conversationId,
696
- "claude.session_id": sessionId,
697
- "claude.source": "message",
698
- };
699
-
700
- if (ids.agentId) {
701
- attrs["gen_ai.agent.id"] = ids.agentId;
702
- }
703
-
704
- const span = this._tracer.startSpan("claude.session", {
705
- kind: SpanKind.CLIENT,
706
- attributes: attrs,
707
- });
708
-
709
- // Add model if present in init message
710
- const model = msg.model as string | undefined;
711
- if (model) {
712
- span.setAttribute("gen_ai.request.model", model);
713
- }
714
-
715
- // Add tools if present
716
- const tools = msg.tools as string[] | undefined;
717
- if (tools && tools.length > 0) {
718
- span.setAttribute("claude.tools", tools.join(","));
719
- span.setAttribute(
720
- "gen_ai.tool.definitions",
721
- JSON.stringify(tools.map((name) => ({ name }))),
722
- );
723
- }
724
-
725
- this._sessionSpans.set(sessionId, span);
726
-
727
- // Initialize session usage, including any pending input prompt and system instructions
728
- const sessionData: {
729
- input: number;
730
- output: number;
731
- cacheRead: number;
732
- cacheCreation: number;
733
- model?: string;
734
- inputPrompt?: string;
735
- responseContent?: string;
736
- toolNames?: string[];
737
- systemInstructions?: string;
738
- } = { input: 0, output: 0, cacheRead: 0, cacheCreation: 0, model };
739
-
740
- if (tools && tools.length > 0) {
741
- sessionData.toolNames = tools;
742
- }
743
-
744
- if (this._pendingInputPrompt) {
745
- sessionData.inputPrompt = this._pendingInputPrompt;
746
- this._pendingInputPrompt = undefined;
747
- }
748
-
749
- if (this._pendingSystemInstructions) {
750
- sessionData.systemInstructions = this._pendingSystemInstructions;
751
- this._pendingSystemInstructions = undefined;
752
- }
753
-
754
- this._sessionUsage.set(sessionId, sessionData);
755
- logger.info(`Created session span for ${sessionId}`);
756
- }
757
-
758
- /**
759
- * End a session span from a result message.
760
- * This provides an alternative to the SessionEnd hook which has timing issues.
761
- */
762
- private _endSessionFromMessage(
763
- sessionId: string,
764
- msg: Record<string, unknown>,
765
- ): void {
766
- const span = this._sessionSpans.get(sessionId);
767
- if (!span) {
768
- logger.debug(`No session span found for ${sessionId}, nothing to end`);
769
- return;
770
- }
771
-
772
- const usage = this._sessionUsage.get(sessionId);
773
-
774
- // Use converter to set GenAI semantic convention attributes
775
- const otelAttrs = convertClaudeSessionToOtelAttributes({
776
- sessionId,
777
- inputPrompt: usage?.inputPrompt,
778
- responseContent: usage?.responseContent,
779
- responseBlocks: usage?.responseBlocks,
780
- model: usage?.model,
781
- inputTokens: usage?.input,
782
- outputTokens: usage?.output,
783
- cacheReadInputTokens: usage?.cacheRead || undefined,
784
- cacheCreationInputTokens: usage?.cacheCreation || undefined,
785
- costUsd: usage?.cost,
786
- stopReason: usage?.stopReason,
787
- responseId: usage?.responseId,
788
- toolNames: usage?.toolNames,
789
- systemInstructions: usage?.systemInstructions,
790
- });
791
-
792
- // Apply all converted attributes to the span
793
- for (const [key, value] of Object.entries(otelAttrs)) {
794
- span.setAttribute(key, value);
795
- }
796
-
797
- // Set subtype/reason from result message
798
- const subtype = msg.subtype as string | undefined;
799
- if (subtype) {
800
- span.setAttribute("claude.result_subtype", subtype);
801
- if (subtype === "success") {
802
- span.setStatus({ code: SpanStatusCode.OK });
803
- } else {
804
- span.setStatus({
805
- code: SpanStatusCode.ERROR,
806
- message: subtype,
807
- });
808
- }
809
- } else {
810
- span.setStatus({ code: SpanStatusCode.OK });
811
- }
812
-
813
- span.end();
814
- this._sessionSpans.delete(sessionId);
815
- this._sessionUsage.delete(sessionId);
816
- logger.info(
817
- `Ended session span for ${sessionId}: ${usage?.input || 0} input, ${usage?.output || 0} output tokens`,
818
- );
819
- }
820
-
821
- private async _onSessionStart(
822
- input: ClaudeHookInput,
823
- _toolUseID?: string,
824
- _options?: { signal: AbortSignal },
825
- ): Promise<ClaudeHookOutput> {
826
- void _toolUseID;
827
- void _options;
828
- const sessionInput = input as SessionStartHookInput;
829
- logger.debug(`SessionStart hook: ${sessionInput.session_id}`);
830
-
831
- // Resolve identity via _resolveIdentity so baggage on the active context
832
- // takes precedence over constructor defaults, matching the message-init
833
- // path in _startSessionFromMessage. Without this, the SessionStart hook
834
- // would always stamp the constructor-time agentName / sessionId even
835
- // when the caller wrapped the query in withAgent() / withConversation().
836
- const ids = this._resolveIdentity(sessionInput.session_id);
837
- const attrs: Attributes = {
838
- "gen_ai.provider.name": "anthropic",
839
- "gen_ai.operation.name": "chat",
840
- "gen_ai.agent.name": ids.agentName,
841
- "gen_ai.conversation.id": ids.conversationId,
842
- "claude.session_id": sessionInput.session_id,
843
- "claude.source": sessionInput.source,
844
- };
845
- if (ids.agentId) {
846
- attrs["gen_ai.agent.id"] = ids.agentId;
847
- }
848
-
849
- const span = this._tracer.startSpan("claude.session", {
850
- kind: SpanKind.CLIENT,
851
- attributes: attrs,
852
- });
853
-
854
- if (sessionInput.model) {
855
- span.setAttribute("gen_ai.request.model", sessionInput.model);
856
- }
857
- if (sessionInput.agent_type) {
858
- span.setAttribute("claude.agent_type", sessionInput.agent_type);
859
- }
860
-
861
- this._sessionSpans.set(sessionInput.session_id, span);
862
-
863
- // Initialize session usage, consuming any pending prompts/instructions
864
- const sessionUsage: {
865
- input: number;
866
- output: number;
867
- cacheRead: number;
868
- cacheCreation: number;
869
- inputPrompt?: string;
870
- systemInstructions?: string;
871
- } = {
872
- input: 0,
873
- output: 0,
874
- cacheRead: 0,
875
- cacheCreation: 0,
876
- };
877
- if (this._pendingInputPrompt) {
878
- sessionUsage.inputPrompt = this._pendingInputPrompt;
879
- this._pendingInputPrompt = undefined;
880
- }
881
- if (this._pendingSystemInstructions) {
882
- sessionUsage.systemInstructions = this._pendingSystemInstructions;
883
- this._pendingSystemInstructions = undefined;
884
- }
885
- this._sessionUsage.set(sessionInput.session_id, sessionUsage);
886
-
887
- return { continue: true };
888
- }
889
-
890
- private async _onSessionEnd(
891
- input: ClaudeHookInput,
892
- _toolUseID?: string,
893
- _options?: { signal: AbortSignal },
894
- ): Promise<ClaudeHookOutput> {
895
- void _toolUseID;
896
- void _options;
897
- const sessionInput = input as SessionEndHookInput;
898
- logger.debug(`SessionEnd hook: ${sessionInput.session_id}`);
899
-
900
- const span = this._sessionSpans.get(sessionInput.session_id);
901
- if (span) {
902
- const usage = this._sessionUsage.get(sessionInput.session_id);
903
- if (usage) {
904
- // Use converter for consistent gen_ai attribute mapping
905
- const otelAttrs = convertClaudeSessionToOtelAttributes({
906
- sessionId: sessionInput.session_id,
907
- inputPrompt: usage.inputPrompt,
908
- responseContent: usage.responseContent,
909
- responseBlocks: usage.responseBlocks,
910
- model: usage.model,
911
- inputTokens: usage.input,
912
- outputTokens: usage.output,
913
- cacheReadInputTokens: usage.cacheRead || undefined,
914
- cacheCreationInputTokens: usage.cacheCreation || undefined,
915
- costUsd: usage.cost,
916
- stopReason: usage.stopReason,
917
- responseId: usage.responseId,
918
- toolNames: usage.toolNames,
919
- systemInstructions: usage.systemInstructions,
920
- });
921
- for (const [key, value] of Object.entries(otelAttrs)) {
922
- span.setAttribute(key, value);
923
- }
924
- }
925
-
926
- span.setAttribute("claude.exit_reason", sessionInput.reason);
927
- span.setStatus({ code: SpanStatusCode.OK });
928
- span.end();
929
- this._sessionSpans.delete(sessionInput.session_id);
930
- this._sessionUsage.delete(sessionInput.session_id);
931
- logger.debug(`Ended session span for ${sessionInput.session_id}`);
932
- }
933
-
934
- return { continue: true };
935
- }
936
-
937
- private async _onPreToolUse(
938
- input: ClaudeHookInput,
939
- _toolUseID?: string,
940
- _options?: { signal: AbortSignal },
941
- ): Promise<ClaudeHookOutput> {
942
- void _toolUseID;
943
- void _options;
944
- const toolInput = input as PreToolUseHookInput;
945
- logger.debug(
946
- `PreToolUse: ${toolInput.tool_name} (${toolInput.tool_use_id})`,
947
- );
948
-
949
- const parentSpan = this._sessionSpans.get(toolInput.session_id);
950
- const ctx = parentSpan
951
- ? otelTrace.setSpan(otelContext.active(), parentSpan)
952
- : undefined;
953
-
954
- const span = this._tracer.startSpan(
955
- `execute_tool ${toolInput.tool_name}`,
956
- {
957
- kind: SpanKind.INTERNAL,
958
- attributes: {
959
- "gen_ai.operation.name": "execute_tool",
960
- "gen_ai.tool.name": toolInput.tool_name,
961
- "gen_ai.tool.type": "function",
962
- "gen_ai.tool.call.id": toolInput.tool_use_id,
963
- "claude.session_id": toolInput.session_id,
964
- "claude.tool_use_id": toolInput.tool_use_id,
965
- },
966
- },
967
- ctx,
968
- );
969
-
970
- if (toolInput.tool_input) {
971
- try {
972
- span.setAttribute(
973
- "gen_ai.tool.call.arguments",
974
- JSON.stringify(toolInput.tool_input),
975
- );
976
- } catch {
977
- span.setAttribute(
978
- "gen_ai.tool.call.arguments",
979
- String(toolInput.tool_input),
980
- );
981
- }
982
- }
983
-
984
- this._toolSpans.set(toolInput.tool_use_id, span);
985
-
986
- return { continue: true };
987
- }
988
-
989
- private async _onPostToolUse(
990
- input: ClaudeHookInput,
991
- _toolUseID?: string,
992
- _options?: { signal: AbortSignal },
993
- ): Promise<ClaudeHookOutput> {
994
- void _toolUseID;
995
- void _options;
996
- const toolInput = input as PostToolUseHookInput;
997
- logger.debug(
998
- `PostToolUse: ${toolInput.tool_name} (${toolInput.tool_use_id})`,
999
- );
1000
-
1001
- const span = this._toolSpans.get(toolInput.tool_use_id);
1002
-
1003
- if (span) {
1004
- if (toolInput.tool_response !== undefined) {
1005
- try {
1006
- span.setAttribute(
1007
- "gen_ai.tool.call.result",
1008
- JSON.stringify(toolInput.tool_response),
1009
- );
1010
- } catch {
1011
- span.setAttribute(
1012
- "gen_ai.tool.call.result",
1013
- String(toolInput.tool_response),
1014
- );
1015
- }
1016
- }
1017
- span.end();
1018
- this._toolSpans.delete(toolInput.tool_use_id);
1019
- }
1020
-
1021
- return { continue: true };
1022
- }
1023
-
1024
- private async _onSubagentStart(
1025
- input: ClaudeHookInput,
1026
- _toolUseID?: string,
1027
- _options?: { signal: AbortSignal },
1028
- ): Promise<ClaudeHookOutput> {
1029
- void _toolUseID;
1030
- void _options;
1031
- const subagentInput = input as SubagentStartHookInput;
1032
- logger.debug(
1033
- `SubagentStart: ${subagentInput.agent_type} (${subagentInput.agent_id})`,
1034
- );
1035
-
1036
- const parentSpan = this._sessionSpans.get(subagentInput.session_id);
1037
- const ctx = parentSpan
1038
- ? otelTrace.setSpan(otelContext.active(), parentSpan)
1039
- : undefined;
1040
-
1041
- const span = this._tracer.startSpan(
1042
- `subagent.${subagentInput.agent_type}`,
1043
- {
1044
- kind: SpanKind.INTERNAL,
1045
- attributes: {
1046
- "gen_ai.agent.name": subagentInput.agent_type,
1047
- "claude.session_id": subagentInput.session_id,
1048
- "claude.agent_id": subagentInput.agent_id,
1049
- },
1050
- },
1051
- ctx,
1052
- );
1053
-
1054
- this._subagentSpans.set(subagentInput.agent_id, span);
1055
-
1056
- return { continue: true };
1057
- }
1058
-
1059
- private async _onSubagentStop(
1060
- input: ClaudeHookInput,
1061
- _toolUseID?: string,
1062
- _options?: { signal: AbortSignal },
1063
- ): Promise<ClaudeHookOutput> {
1064
- void _toolUseID;
1065
- void _options;
1066
- const subagentInput = input as SubagentStopHookInput;
1067
- logger.debug(
1068
- `SubagentStop: ${subagentInput.agent_type} (${subagentInput.agent_id})`,
1069
- );
1070
-
1071
- const span = this._subagentSpans.get(subagentInput.agent_id);
1072
-
1073
- if (span) {
1074
- span.setStatus({ code: SpanStatusCode.OK });
1075
- span.end();
1076
- this._subagentSpans.delete(subagentInput.agent_id);
1077
- }
1078
-
1079
- return { continue: true };
1080
- }
1081
-
1082
- /**
1083
- * Force-flush any buffered spans to the Introspection backend.
1084
- *
1085
- * @returns A promise that resolves once the flush completes.
1086
- */
1087
- async forceFlush(): Promise<void> {
1088
- await this._tracerProvider.forceFlush();
1089
- }
1090
-
1091
- /**
1092
- * Shut down the hooks, ending any in-flight spans and flushing all
1093
- * pending data.
1094
- *
1095
- * @returns A promise that resolves once shutdown is complete.
1096
- */
1097
- async shutdown(): Promise<void> {
1098
- // End any remaining spans
1099
- for (const [, span] of this._sessionSpans) {
1100
- span.end();
1101
- }
1102
- for (const [, span] of this._toolSpans) {
1103
- span.end();
1104
- }
1105
- for (const [, span] of this._subagentSpans) {
1106
- span.end();
1107
- }
1108
-
1109
- this._sessionSpans.clear();
1110
- this._toolSpans.clear();
1111
- this._subagentSpans.clear();
1112
- this._sessionUsage.clear();
1113
-
1114
- await this._tracerProvider.shutdown();
1115
- }
1116
- }