@introspection-sdk/introspection-node 0.19.1 → 0.21.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.
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 +4 -7
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +3 -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 +18 -60
  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 -266
  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,619 +0,0 @@
1
- /**
2
- * Lightweight Anthropic instrumentor for Introspection SDK.
3
- *
4
- * Captures the full Anthropic response including thinking blocks (extended
5
- * thinking) with signatures, which third-party instrumentors drop.
6
- *
7
- * Supports both non-streaming (`messages.create`) and streaming
8
- * (`messages.create({ stream: true })`) calls.
9
- *
10
- * @example
11
- * ```ts
12
- * import { AnthropicInstrumentor } from "@introspection-sdk/introspection-node";
13
- *
14
- * const instrumentor = new AnthropicInstrumentor();
15
- * instrumentor.instrument({ tracerProvider: provider });
16
- * // All client.messages.create calls are now traced
17
- * ```
18
- */
19
-
20
- import type {
21
- InputMessage,
22
- OutputMessage,
23
- MessagePart,
24
- ReasoningPart,
25
- } from "@introspection-sdk/types";
26
- import { providerCostAttributes } from "@introspection-sdk/types";
27
-
28
- import {
29
- trace,
30
- context as otelContext,
31
- propagation,
32
- SpanKind,
33
- SpanStatusCode,
34
- } from "@opentelemetry/api";
35
- import type { Tracer, Span, TracerProvider } from "@opentelemetry/api";
36
-
37
- // ---------------------------------------------------------------------------
38
- // Converters
39
- // ---------------------------------------------------------------------------
40
-
41
- /** Sentinel value for redacted thinking blocks — content was encrypted by safety systems. */
42
- export const REDACTED_THINKING_CONTENT = "[redacted]";
43
-
44
- function blockToParts(block: Record<string, unknown>): MessagePart[] {
45
- const bt = (block.type as string) || "";
46
-
47
- if (bt === "text") {
48
- return [{ type: "text", content: (block.text as string) || "" }];
49
- }
50
-
51
- if (bt === "thinking") {
52
- const part: ReasoningPart = {
53
- type: "reasoning",
54
- content: (block.thinking as string) || REDACTED_THINKING_CONTENT,
55
- signature: (block.signature as string) || undefined,
56
- provider_name: "anthropic",
57
- };
58
- return [part];
59
- }
60
-
61
- if (bt === "redacted_thinking") {
62
- return [
63
- {
64
- type: "reasoning",
65
- content: REDACTED_THINKING_CONTENT,
66
- signature: (block.data as string) || undefined,
67
- provider_name: "anthropic",
68
- },
69
- ];
70
- }
71
-
72
- if (bt === "tool_use") {
73
- return [
74
- {
75
- type: "tool_call",
76
- id: (block.id as string) || "",
77
- name: (block.name as string) || "",
78
- arguments: block.input,
79
- },
80
- ];
81
- }
82
-
83
- if (bt === "tool_result") {
84
- return [
85
- {
86
- type: "tool_call_response",
87
- id: (block.tool_use_id as string) || "",
88
- response: block.content != null ? String(block.content) : "",
89
- },
90
- ];
91
- }
92
-
93
- return [];
94
- }
95
-
96
- function convertAnthropicInput(
97
- messages: Array<Record<string, unknown>>,
98
- ): InputMessage[] {
99
- const result: InputMessage[] = [];
100
- for (const msg of messages) {
101
- const role = (msg.role as string) || "user";
102
- const content = msg.content;
103
- if (typeof content === "string") {
104
- result.push({
105
- role: role as InputMessage["role"],
106
- parts: [{ type: "text", content }],
107
- });
108
- } else if (Array.isArray(content)) {
109
- const parts: MessagePart[] = [];
110
- for (const block of content) {
111
- const rec =
112
- typeof block === "object" && block !== null
113
- ? (block as Record<string, unknown>)
114
- : { type: "text", text: String(block) };
115
- parts.push(...blockToParts(rec));
116
- }
117
- if (parts.length > 0) {
118
- result.push({ role: role as InputMessage["role"], parts });
119
- }
120
- }
121
- }
122
- return result;
123
- }
124
-
125
- function convertAnthropicOutput(
126
- content: Array<Record<string, unknown>>,
127
- ): OutputMessage[] {
128
- const parts: MessagePart[] = [];
129
- let hasToolCalls = false;
130
- for (const block of content) {
131
- const newParts = blockToParts(block);
132
- for (const p of newParts) {
133
- if (p.type === "tool_call") hasToolCalls = true;
134
- }
135
- parts.push(...newParts);
136
- }
137
- if (parts.length === 0) return [];
138
- return [
139
- {
140
- role: "assistant",
141
- parts,
142
- finish_reason: hasToolCalls ? "tool-calls" : "stop",
143
- },
144
- ];
145
- }
146
-
147
- // ---------------------------------------------------------------------------
148
- // Span helpers
149
- // ---------------------------------------------------------------------------
150
-
151
- function serializeMessages(msgs: InputMessage[] | OutputMessage[]): string {
152
- return JSON.stringify(
153
- msgs.map((m) => {
154
- const obj: Record<string, unknown> = { role: m.role, parts: m.parts };
155
- if ("finish_reason" in m && m.finish_reason) {
156
- obj.finish_reason = m.finish_reason;
157
- }
158
- return obj;
159
- }),
160
- );
161
- }
162
-
163
- function startSpan(
164
- tracer: Tracer,
165
- model: string,
166
- conversationId?: string,
167
- ): { span: Span; token: null } {
168
- const span = tracer.startSpan("chat", {
169
- kind: SpanKind.CLIENT,
170
- attributes: {
171
- "gen_ai.provider.name": "anthropic",
172
- "gen_ai.operation.name": "chat",
173
- "gen_ai.request.model": model,
174
- "openinference.span.kind": "LLM",
175
- ...(conversationId
176
- ? { "gen_ai.conversation.id": conversationId }
177
- : undefined),
178
- },
179
- });
180
- return { span, token: null };
181
- }
182
-
183
- function setRequestAttrs(span: Span, kwargs: Record<string, unknown>): void {
184
- const messages = (kwargs.messages as Array<Record<string, unknown>>) || [];
185
- const inputMsgs = convertAnthropicInput(messages);
186
- if (inputMsgs.length > 0) {
187
- span.setAttribute("gen_ai.input.messages", serializeMessages(inputMsgs));
188
- }
189
-
190
- const system = kwargs.system;
191
- if (system) {
192
- let sysVal: string;
193
- if (typeof system === "string") {
194
- sysVal = JSON.stringify([{ type: "text", content: system }]);
195
- } else if (Array.isArray(system)) {
196
- // Anthropic block format: [{type:"text", text:"...", cache_control:{...}}]
197
- // Normalize to gen_ai format: [{type:"text", content:"..."}]
198
- sysVal = JSON.stringify(
199
- (system as Array<Record<string, unknown>>).map((block) => ({
200
- type: "text",
201
- content:
202
- typeof block.text === "string"
203
- ? block.text
204
- : typeof block.content === "string"
205
- ? block.content
206
- : "",
207
- })),
208
- );
209
- } else {
210
- sysVal = JSON.stringify(system);
211
- }
212
- span.setAttribute("gen_ai.system_instructions", sysVal);
213
- }
214
-
215
- const tools = kwargs.tools as Array<Record<string, unknown>> | undefined;
216
- if (tools && tools.length > 0) {
217
- const defs = tools.map((t) => ({
218
- name: t.name || "",
219
- description: t.description,
220
- parameters: t.input_schema,
221
- }));
222
- span.setAttribute("gen_ai.tool.definitions", JSON.stringify(defs));
223
- }
224
- }
225
-
226
- function setResponseAttrs(span: Span, response: Record<string, unknown>): void {
227
- const content = response.content as
228
- Array<Record<string, unknown>> | undefined;
229
- if (content) {
230
- const outputMsgs = convertAnthropicOutput(content);
231
- if (outputMsgs.length > 0) {
232
- span.setAttribute(
233
- "gen_ai.output.messages",
234
- serializeMessages(outputMsgs),
235
- );
236
- }
237
- }
238
-
239
- if (response.id) span.setAttribute("gen_ai.response.id", String(response.id));
240
- if (response.model)
241
- span.setAttribute("gen_ai.response.model", String(response.model));
242
-
243
- const usage = response.usage as Record<string, number> | undefined;
244
- if (usage) {
245
- if (usage.input_tokens != null)
246
- span.setAttribute("gen_ai.usage.input_tokens", usage.input_tokens);
247
- if (usage.output_tokens != null)
248
- span.setAttribute("gen_ai.usage.output_tokens", usage.output_tokens);
249
- if (usage.cache_read_input_tokens != null)
250
- span.setAttribute(
251
- "gen_ai.usage.cache_read.input_tokens",
252
- usage.cache_read_input_tokens,
253
- );
254
- if (usage.cache_creation_input_tokens != null)
255
- span.setAttribute(
256
- "gen_ai.usage.cache_creation.input_tokens",
257
- usage.cache_creation_input_tokens,
258
- );
259
- // Provider-reported cost (e.g. OpenRouter usage.cost) when present.
260
- span.setAttributes(providerCostAttributes(usage));
261
- }
262
-
263
- span.setStatus({ code: SpanStatusCode.OK });
264
- }
265
-
266
- // ---------------------------------------------------------------------------
267
- // Stream wrapper
268
- // ---------------------------------------------------------------------------
269
-
270
- interface StreamEvent {
271
- type: string;
272
- message?: Record<string, unknown>;
273
- content_block?: Record<string, unknown>;
274
- delta?: Record<string, unknown>;
275
- index?: number;
276
- }
277
-
278
- class TracedStream {
279
- private inner: AsyncIterable<StreamEvent>;
280
- private span: Span;
281
- private blocks: Array<Record<string, unknown>> = [];
282
- private currentBlock: Record<string, unknown> | null = null;
283
- private responseId: string | null = null;
284
- private responseModel: string | null = null;
285
- private inputTokens = 0;
286
- private outputTokens = 0;
287
- private cacheReadInputTokens = 0;
288
- private cacheCreationInputTokens = 0;
289
- private costAttrs: Record<string, number> = {};
290
- private finalized = false;
291
-
292
- constructor(inner: AsyncIterable<StreamEvent>, span: Span) {
293
- this.inner = inner;
294
- this.span = span;
295
- }
296
-
297
- async *[Symbol.asyncIterator](): AsyncIterableIterator<StreamEvent> {
298
- try {
299
- for await (const event of this.inner) {
300
- this.processEvent(event);
301
- yield event;
302
- }
303
- } finally {
304
- this.finalize();
305
- }
306
- }
307
-
308
- private processEvent(event: StreamEvent): void {
309
- if (event.type === "message_start" && event.message) {
310
- this.responseId = (event.message.id as string) || null;
311
- this.responseModel = (event.message.model as string) || null;
312
- const usage = event.message.usage as Record<string, number> | undefined;
313
- if (usage) {
314
- this.inputTokens = usage.input_tokens || 0;
315
- this.cacheReadInputTokens = usage.cache_read_input_tokens || 0;
316
- this.cacheCreationInputTokens = usage.cache_creation_input_tokens || 0;
317
- Object.assign(this.costAttrs, providerCostAttributes(usage));
318
- }
319
- } else if (event.type === "content_block_start" && event.content_block) {
320
- const bt = (event.content_block.type as string) || "";
321
- this.currentBlock = { type: bt };
322
- if (bt === "thinking") {
323
- this.currentBlock.thinking = "";
324
- this.currentBlock.signature = "";
325
- } else if (bt === "text") {
326
- this.currentBlock.text = "";
327
- }
328
- } else if (event.type === "content_block_delta" && event.delta) {
329
- if (!this.currentBlock) return;
330
- const dt = (event.delta.type as string) || "";
331
- if (dt === "thinking_delta") {
332
- this.currentBlock.thinking =
333
- ((this.currentBlock.thinking as string) || "") +
334
- ((event.delta.thinking as string) || "");
335
- } else if (dt === "text_delta") {
336
- this.currentBlock.text =
337
- ((this.currentBlock.text as string) || "") +
338
- ((event.delta.text as string) || "");
339
- } else if (dt === "signature_delta") {
340
- this.currentBlock.signature =
341
- ((this.currentBlock.signature as string) || "") +
342
- ((event.delta.signature as string) || "");
343
- }
344
- } else if (event.type === "content_block_stop") {
345
- if (this.currentBlock) {
346
- this.blocks.push(this.currentBlock);
347
- this.currentBlock = null;
348
- }
349
- } else if (event.type === "message_delta") {
350
- const usage = (event as unknown as Record<string, unknown>).usage as
351
- Record<string, number> | undefined;
352
- if (usage) {
353
- this.outputTokens = usage.output_tokens || 0;
354
- Object.assign(this.costAttrs, providerCostAttributes(usage));
355
- }
356
- }
357
- }
358
-
359
- private finalize(): void {
360
- if (this.finalized) return;
361
- this.finalized = true;
362
-
363
- if (this.blocks.length > 0) {
364
- const outputMsgs = convertAnthropicOutput(this.blocks);
365
- if (outputMsgs.length > 0) {
366
- this.span.setAttribute(
367
- "gen_ai.output.messages",
368
- serializeMessages(outputMsgs),
369
- );
370
- }
371
- }
372
- if (this.responseId)
373
- this.span.setAttribute("gen_ai.response.id", this.responseId);
374
- if (this.responseModel)
375
- this.span.setAttribute("gen_ai.response.model", this.responseModel);
376
- if (this.inputTokens)
377
- this.span.setAttribute("gen_ai.usage.input_tokens", this.inputTokens);
378
- if (this.outputTokens)
379
- this.span.setAttribute("gen_ai.usage.output_tokens", this.outputTokens);
380
- if (this.cacheReadInputTokens)
381
- this.span.setAttribute(
382
- "gen_ai.usage.cache_read.input_tokens",
383
- this.cacheReadInputTokens,
384
- );
385
- if (this.cacheCreationInputTokens)
386
- this.span.setAttribute(
387
- "gen_ai.usage.cache_creation.input_tokens",
388
- this.cacheCreationInputTokens,
389
- );
390
- // Provider-reported cost (e.g. OpenRouter usage.cost) when present.
391
- this.span.setAttributes(this.costAttrs);
392
- this.span.setStatus({ code: SpanStatusCode.OK });
393
- this.span.end();
394
- }
395
- }
396
-
397
- // ---------------------------------------------------------------------------
398
- // Public API
399
- // ---------------------------------------------------------------------------
400
-
401
- /**
402
- * Traced wrapper around `client.messages.create(kwargs)`.
403
- * Creates a gen_ai span with full Anthropic response including thinking blocks.
404
- */
405
- export async function tracedMessagesCreate(
406
- tracer: Tracer,
407
- client: { messages: { create: (...args: unknown[]) => Promise<unknown> } },
408
- kwargs: Record<string, unknown>,
409
- conversationId?: string,
410
- ): Promise<unknown> {
411
- const { span } = startSpan(
412
- tracer,
413
- (kwargs.model as string) || "unknown",
414
- conversationId,
415
- );
416
- try {
417
- setRequestAttrs(span, kwargs);
418
- const response = (await client.messages.create(kwargs)) as Record<
419
- string,
420
- unknown
421
- >;
422
- setResponseAttrs(span, response);
423
- return response;
424
- } catch (e) {
425
- span.setStatus({
426
- code: SpanStatusCode.ERROR,
427
- message: String(e),
428
- });
429
- throw e;
430
- } finally {
431
- span.end();
432
- }
433
- }
434
-
435
- /**
436
- * Auto-instrumentor that patches `Anthropic.messages.create` to add tracing.
437
- *
438
- * Captures all content blocks including thinking (extended thinking) with
439
- * signatures. Supports both non-streaming and streaming calls.
440
- */
441
- /**
442
- * Auto-instrumentor that wraps an Anthropic client instance to add tracing.
443
- *
444
- * Captures all content blocks including thinking (extended thinking) with
445
- * signatures. Supports both non-streaming and streaming calls.
446
- */
447
- /**
448
- * Build the traced replacement for `messages.create`.
449
- *
450
- * Shared by {@link AnthropicInstrumentor.instrument} (patches a single client
451
- * instance) and {@link AnthropicInstrumentor.instrumentClass} (patches
452
- * `Anthropic.Messages.prototype.create` so every client is auto-traced). The
453
- * returned function preserves the call-site `this`, so it works whether it is
454
- * assigned to an instance property or onto the class prototype.
455
- */
456
- function buildTracedCreate(
457
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
458
- origCreate: (...args: any[]) => any,
459
- tracer: Tracer,
460
- defaultConversationId: string,
461
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
462
- ): (...args: any[]) => any {
463
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
464
- return function tracedCreate(this: unknown, ...args: any[]) {
465
- const kwargs = args[0] as Record<string, unknown>;
466
- const isStream = kwargs?.stream === true;
467
-
468
- // Baggage on the active context overrides the conversation ID bound at
469
- // instrument() time, so callers can wrap a request in
470
- // IntrospectionClient.withConversation() to scope a single call to a
471
- // distinct conversation without re-instrumenting the client.
472
- const baggage = propagation.getBaggage(otelContext.active());
473
- const baggageConvId = baggage?.getEntry("gen_ai.conversation.id")?.value;
474
- const baggageAgentName = baggage?.getEntry("gen_ai.agent.name")?.value;
475
- const baggageAgentId = baggage?.getEntry("gen_ai.agent.id")?.value;
476
-
477
- const { span } = startSpan(
478
- tracer,
479
- (kwargs?.model as string) || "unknown",
480
- baggageConvId ?? defaultConversationId,
481
- );
482
- if (baggageAgentName) {
483
- span.setAttribute("gen_ai.agent.name", baggageAgentName);
484
- }
485
- if (baggageAgentId) {
486
- span.setAttribute("gen_ai.agent.id", baggageAgentId);
487
- }
488
- setRequestAttrs(span, kwargs || {});
489
-
490
- if (isStream) {
491
- const streamPromise = origCreate.apply(this, args) as Promise<
492
- AsyncIterable<StreamEvent>
493
- >;
494
- return streamPromise.then(
495
- (stream: AsyncIterable<StreamEvent>) => new TracedStream(stream, span),
496
- (err: Error) => {
497
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });
498
- span.end();
499
- throw err;
500
- },
501
- );
502
- }
503
-
504
- const resultPromise = origCreate.apply(this, args) as Promise<
505
- Record<string, unknown>
506
- >;
507
- return resultPromise.then(
508
- (response: Record<string, unknown>) => {
509
- setResponseAttrs(span, response);
510
- span.end();
511
- return response;
512
- },
513
- (err: Error) => {
514
- span.setStatus({ code: SpanStatusCode.ERROR, message: String(err) });
515
- span.end();
516
- throw err;
517
- },
518
- );
519
- };
520
- }
521
-
522
- /**
523
- * Marker placed on a patched `Messages.prototype.create` so repeated
524
- * `instrumentClass()` calls (e.g. a second `introspection.init()`) are no-ops.
525
- */
526
- const PATCHED = Symbol.for("introspection.anthropic.patched");
527
-
528
- export class AnthropicInstrumentor {
529
- private tracer: Tracer | null = null;
530
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
531
- private patchedClients: Array<{ client: any; originalCreate: any }> = [];
532
- private protoRestores: Array<() => void> = [];
533
- private conversationId: string | undefined;
534
-
535
- instrument(opts: {
536
- tracerProvider?: TracerProvider;
537
- /** The Anthropic client instance to instrument. */
538
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
539
- client: any;
540
- /**
541
- * Conversation ID shared across all spans produced by this instrumentor.
542
- * Enables the Introspection processor to reliably link multi-turn API calls
543
- * into a single conversation without relying on history-hash matching.
544
- * Defaults to a random UUID generated at instrument() time.
545
- */
546
- conversationId?: string;
547
- }): void {
548
- const { client } = opts;
549
- if (!client?.messages?.create) {
550
- throw new Error(
551
- "Invalid client — pass an Anthropic client instance (new Anthropic())",
552
- );
553
- }
554
-
555
- this.conversationId = opts.conversationId ?? crypto.randomUUID();
556
-
557
- const provider = opts.tracerProvider ?? trace.getTracerProvider();
558
- this.tracer = provider.getTracer("introspection-anthropic");
559
-
560
- const origCreate = client.messages.create.bind(client.messages);
561
- this.patchedClients.push({ client, originalCreate: origCreate });
562
-
563
- client.messages.create = buildTracedCreate(
564
- origCreate,
565
- this.tracer,
566
- this.conversationId,
567
- );
568
- }
569
-
570
- /**
571
- * Patch `Anthropic.Messages.prototype.create` so **every** client constructed
572
- * from the given module is traced — the one-liner path used by
573
- * `introspection.init()`. Idempotent: a second call is a no-op.
574
- *
575
- * @param opts.anthropic The `@anthropic-ai/sdk` module's default export
576
- * (the `Anthropic` class). Its `.Messages.prototype.create` is patched.
577
- */
578
- instrumentClass(opts: {
579
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
580
- anthropic: any;
581
- tracerProvider?: TracerProvider;
582
- conversationId?: string;
583
- }): void {
584
- const Messages = opts.anthropic?.Messages;
585
- const proto = Messages?.prototype;
586
- if (typeof proto?.create !== "function") {
587
- throw new Error(
588
- "Invalid Anthropic module — expected `Anthropic.Messages.prototype.create`",
589
- );
590
- }
591
- if (proto.create[PATCHED]) return;
592
-
593
- this.conversationId = opts.conversationId ?? crypto.randomUUID();
594
- const provider = opts.tracerProvider ?? trace.getTracerProvider();
595
- this.tracer = provider.getTracer("introspection-anthropic");
596
-
597
- const origCreate = proto.create;
598
- const patched = buildTracedCreate(
599
- origCreate,
600
- this.tracer,
601
- this.conversationId,
602
- ) as ((...args: unknown[]) => unknown) & { [PATCHED]?: boolean };
603
- patched[PATCHED] = true;
604
- proto.create = patched;
605
- this.protoRestores.push(() => {
606
- proto.create = origCreate;
607
- });
608
- }
609
-
610
- uninstrument(): void {
611
- for (const { client, originalCreate } of this.patchedClients) {
612
- client.messages.create = originalCreate;
613
- }
614
- this.patchedClients = [];
615
- for (const restore of this.protoRestores) restore();
616
- this.protoRestores = [];
617
- this.tracer = null;
618
- }
619
- }