@introspection-sdk/introspection-node 0.4.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.
- package/.turbo/turbo-build.log +4 -0
- package/.turbo/turbo-check-types.log +5 -0
- package/.turbo/turbo-lint.log +5 -0
- package/dist/ai-sdk-integration.d.ts +123 -0
- package/dist/ai-sdk-integration.d.ts.map +1 -0
- package/dist/ai-sdk-integration.js +475 -0
- package/dist/anthropic.d.ts +54 -0
- package/dist/anthropic.d.ts.map +1 -0
- package/dist/anthropic.js +375 -0
- package/dist/claude-hooks.d.ts +328 -0
- package/dist/claude-hooks.d.ts.map +1 -0
- package/dist/claude-hooks.js +571 -0
- package/dist/claude-wrapper.d.ts +93 -0
- package/dist/claude-wrapper.d.ts.map +1 -0
- package/dist/claude-wrapper.js +154 -0
- package/dist/client.d.ts +157 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +394 -0
- package/dist/converters/ai-sdk.d.ts +65 -0
- package/dist/converters/ai-sdk.d.ts.map +1 -0
- package/dist/converters/ai-sdk.js +238 -0
- package/dist/converters/claude.d.ts +146 -0
- package/dist/converters/claude.d.ts.map +1 -0
- package/dist/converters/claude.js +202 -0
- package/dist/converters/index.d.ts +9 -0
- package/dist/converters/index.d.ts.map +1 -0
- package/dist/converters/index.js +8 -0
- package/dist/converters/openai.d.ts +51 -0
- package/dist/converters/openai.d.ts.map +1 -0
- package/dist/converters/openai.js +268 -0
- package/dist/converters/openclaw.d.ts +16 -0
- package/dist/converters/openclaw.d.ts.map +1 -0
- package/dist/converters/openclaw.js +49 -0
- package/dist/converters/openinference.d.ts +107 -0
- package/dist/converters/openinference.d.ts.map +1 -0
- package/dist/converters/openinference.js +552 -0
- package/dist/converters/vercel.d.ts +21 -0
- package/dist/converters/vercel.d.ts.map +1 -0
- package/dist/converters/vercel.js +208 -0
- package/dist/index.d.ts +38 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/langchain-handler.d.ts +101 -0
- package/dist/langchain-handler.d.ts.map +1 -0
- package/dist/langchain-handler.js +489 -0
- package/dist/mastra-exporter.d.ts +85 -0
- package/dist/mastra-exporter.d.ts.map +1 -0
- package/dist/mastra-exporter.js +471 -0
- package/dist/span-processor.d.ts +62 -0
- package/dist/span-processor.d.ts.map +1 -0
- package/dist/span-processor.js +310 -0
- package/dist/tracing-processor.d.ts +136 -0
- package/dist/tracing-processor.d.ts.map +1 -0
- package/dist/tracing-processor.js +368 -0
- package/dist/types/genai.d.ts +165 -0
- package/dist/types/genai.d.ts.map +1 -0
- package/dist/types/genai.js +82 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +57 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/eslint.config.mjs +4 -0
- package/package.json +78 -0
- package/src/ai-sdk-integration.ts +689 -0
- package/src/anthropic.ts +465 -0
- package/src/claude-hooks.ts +1010 -0
- package/src/claude-wrapper.ts +240 -0
- package/src/client.ts +513 -0
- package/src/converters/ai-sdk.ts +298 -0
- package/src/converters/claude.ts +334 -0
- package/src/converters/index.ts +44 -0
- package/src/converters/openai.ts +302 -0
- package/src/converters/openinference.ts +673 -0
- package/src/converters/vercel.ts +264 -0
- package/src/index.ts +121 -0
- package/src/langchain-handler.ts +722 -0
- package/src/mastra-exporter.ts +650 -0
- package/src/span-processor.ts +418 -0
- package/src/tracing-processor.ts +548 -0
- package/src/types/genai.ts +264 -0
- package/src/types.ts +13 -0
- package/src/utils.ts +69 -0
- package/src/version.ts +2 -0
- package/tsconfig.json +8 -0
package/src/anthropic.ts
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
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 "./types/genai.js";
|
|
26
|
+
|
|
27
|
+
import { trace, SpanKind, SpanStatusCode } from "@opentelemetry/api";
|
|
28
|
+
import type { Tracer, Span } from "@opentelemetry/api";
|
|
29
|
+
import type { BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Converters
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
/** Sentinel value for redacted thinking blocks — content was encrypted by safety systems. */
|
|
36
|
+
export const REDACTED_THINKING_CONTENT = "[redacted]";
|
|
37
|
+
|
|
38
|
+
function blockToParts(block: Record<string, unknown>): MessagePart[] {
|
|
39
|
+
const bt = (block.type as string) || "";
|
|
40
|
+
|
|
41
|
+
if (bt === "text") {
|
|
42
|
+
return [{ type: "text", content: (block.text as string) || "" }];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (bt === "thinking") {
|
|
46
|
+
const part: ReasoningPart = {
|
|
47
|
+
type: "thinking",
|
|
48
|
+
content: (block.thinking as string) || undefined,
|
|
49
|
+
signature: (block.signature as string) || undefined,
|
|
50
|
+
provider_name: "anthropic",
|
|
51
|
+
};
|
|
52
|
+
return [part];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (bt === "redacted_thinking") {
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
type: "thinking",
|
|
59
|
+
content: REDACTED_THINKING_CONTENT,
|
|
60
|
+
signature: (block.data as string) || undefined,
|
|
61
|
+
provider_name: "anthropic",
|
|
62
|
+
} as ReasoningPart,
|
|
63
|
+
];
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (bt === "tool_use") {
|
|
67
|
+
return [
|
|
68
|
+
{
|
|
69
|
+
type: "tool_call",
|
|
70
|
+
id: (block.id as string) || "",
|
|
71
|
+
name: (block.name as string) || "",
|
|
72
|
+
arguments: block.input,
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (bt === "tool_result") {
|
|
78
|
+
return [
|
|
79
|
+
{
|
|
80
|
+
type: "tool_call_response",
|
|
81
|
+
id: (block.tool_use_id as string) || "",
|
|
82
|
+
response: block.content != null ? String(block.content) : "",
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function convertAnthropicInput(
|
|
91
|
+
messages: Array<Record<string, unknown>>,
|
|
92
|
+
): InputMessage[] {
|
|
93
|
+
const result: InputMessage[] = [];
|
|
94
|
+
for (const msg of messages) {
|
|
95
|
+
const role = (msg.role as string) || "user";
|
|
96
|
+
const content = msg.content;
|
|
97
|
+
if (typeof content === "string") {
|
|
98
|
+
result.push({
|
|
99
|
+
role: role as InputMessage["role"],
|
|
100
|
+
parts: [{ type: "text", content }],
|
|
101
|
+
});
|
|
102
|
+
} else if (Array.isArray(content)) {
|
|
103
|
+
const parts: MessagePart[] = [];
|
|
104
|
+
for (const block of content) {
|
|
105
|
+
const rec =
|
|
106
|
+
typeof block === "object" && block !== null
|
|
107
|
+
? (block as Record<string, unknown>)
|
|
108
|
+
: { type: "text", text: String(block) };
|
|
109
|
+
parts.push(...blockToParts(rec));
|
|
110
|
+
}
|
|
111
|
+
if (parts.length > 0) {
|
|
112
|
+
result.push({ role: role as InputMessage["role"], parts });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function convertAnthropicOutput(
|
|
120
|
+
content: Array<Record<string, unknown>>,
|
|
121
|
+
): OutputMessage[] {
|
|
122
|
+
const parts: MessagePart[] = [];
|
|
123
|
+
let hasToolCalls = false;
|
|
124
|
+
for (const block of content) {
|
|
125
|
+
const newParts = blockToParts(block);
|
|
126
|
+
for (const p of newParts) {
|
|
127
|
+
if (p.type === "tool_call") hasToolCalls = true;
|
|
128
|
+
}
|
|
129
|
+
parts.push(...newParts);
|
|
130
|
+
}
|
|
131
|
+
if (parts.length === 0) return [];
|
|
132
|
+
return [
|
|
133
|
+
{
|
|
134
|
+
role: "assistant",
|
|
135
|
+
parts,
|
|
136
|
+
finish_reason: hasToolCalls ? "tool-calls" : "stop",
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
// Span helpers
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
|
|
145
|
+
function serializeMessages(msgs: InputMessage[] | OutputMessage[]): string {
|
|
146
|
+
return JSON.stringify(
|
|
147
|
+
msgs.map((m) => {
|
|
148
|
+
const obj: Record<string, unknown> = { role: m.role, parts: m.parts };
|
|
149
|
+
if ("finish_reason" in m && m.finish_reason) {
|
|
150
|
+
obj.finish_reason = m.finish_reason;
|
|
151
|
+
}
|
|
152
|
+
return obj;
|
|
153
|
+
}),
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function startSpan(tracer: Tracer, model: string): { span: Span; token: null } {
|
|
158
|
+
const span = tracer.startSpan("chat", {
|
|
159
|
+
kind: SpanKind.CLIENT,
|
|
160
|
+
attributes: {
|
|
161
|
+
"gen_ai.system": "anthropic",
|
|
162
|
+
"gen_ai.provider.name": "anthropic",
|
|
163
|
+
"gen_ai.operation.name": "chat",
|
|
164
|
+
"gen_ai.request.model": model,
|
|
165
|
+
"openinference.span.kind": "LLM",
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
return { span, token: null };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function setRequestAttrs(span: Span, kwargs: Record<string, unknown>): void {
|
|
172
|
+
const messages = (kwargs.messages as Array<Record<string, unknown>>) || [];
|
|
173
|
+
const inputMsgs = convertAnthropicInput(messages);
|
|
174
|
+
if (inputMsgs.length > 0) {
|
|
175
|
+
span.setAttribute("gen_ai.input.messages", serializeMessages(inputMsgs));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const system = kwargs.system;
|
|
179
|
+
if (system) {
|
|
180
|
+
const sysVal =
|
|
181
|
+
typeof system === "string"
|
|
182
|
+
? JSON.stringify([{ type: "text", content: system }])
|
|
183
|
+
: JSON.stringify(system);
|
|
184
|
+
span.setAttribute("gen_ai.system_instructions", sysVal);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const tools = kwargs.tools as Array<Record<string, unknown>> | undefined;
|
|
188
|
+
if (tools && tools.length > 0) {
|
|
189
|
+
const defs = tools.map((t) => ({
|
|
190
|
+
name: t.name || "",
|
|
191
|
+
description: t.description,
|
|
192
|
+
parameters: t.input_schema,
|
|
193
|
+
}));
|
|
194
|
+
span.setAttribute("gen_ai.tool.definitions", JSON.stringify(defs));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function setResponseAttrs(span: Span, response: Record<string, unknown>): void {
|
|
199
|
+
const content = response.content as
|
|
200
|
+
| Array<Record<string, unknown>>
|
|
201
|
+
| undefined;
|
|
202
|
+
if (content) {
|
|
203
|
+
const outputMsgs = convertAnthropicOutput(content);
|
|
204
|
+
if (outputMsgs.length > 0) {
|
|
205
|
+
span.setAttribute(
|
|
206
|
+
"gen_ai.output.messages",
|
|
207
|
+
serializeMessages(outputMsgs),
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (response.id) span.setAttribute("gen_ai.response.id", String(response.id));
|
|
213
|
+
if (response.model)
|
|
214
|
+
span.setAttribute("gen_ai.response.model", String(response.model));
|
|
215
|
+
|
|
216
|
+
const usage = response.usage as Record<string, number> | undefined;
|
|
217
|
+
if (usage) {
|
|
218
|
+
if (usage.input_tokens != null)
|
|
219
|
+
span.setAttribute("gen_ai.usage.input_tokens", usage.input_tokens);
|
|
220
|
+
if (usage.output_tokens != null)
|
|
221
|
+
span.setAttribute("gen_ai.usage.output_tokens", usage.output_tokens);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
// Stream wrapper
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
|
|
231
|
+
interface StreamEvent {
|
|
232
|
+
type: string;
|
|
233
|
+
message?: Record<string, unknown>;
|
|
234
|
+
content_block?: Record<string, unknown>;
|
|
235
|
+
delta?: Record<string, unknown>;
|
|
236
|
+
index?: number;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
class TracedStream {
|
|
240
|
+
private inner: AsyncIterable<StreamEvent>;
|
|
241
|
+
private span: Span;
|
|
242
|
+
private blocks: Array<Record<string, unknown>> = [];
|
|
243
|
+
private currentBlock: Record<string, unknown> | null = null;
|
|
244
|
+
private responseId: string | null = null;
|
|
245
|
+
private responseModel: string | null = null;
|
|
246
|
+
private inputTokens = 0;
|
|
247
|
+
private outputTokens = 0;
|
|
248
|
+
private finalized = false;
|
|
249
|
+
|
|
250
|
+
constructor(inner: AsyncIterable<StreamEvent>, span: Span) {
|
|
251
|
+
this.inner = inner;
|
|
252
|
+
this.span = span;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async *[Symbol.asyncIterator](): AsyncIterableIterator<StreamEvent> {
|
|
256
|
+
try {
|
|
257
|
+
for await (const event of this.inner) {
|
|
258
|
+
this.processEvent(event);
|
|
259
|
+
yield event;
|
|
260
|
+
}
|
|
261
|
+
} finally {
|
|
262
|
+
this.finalize();
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private processEvent(event: StreamEvent): void {
|
|
267
|
+
if (event.type === "message_start" && event.message) {
|
|
268
|
+
this.responseId = (event.message.id as string) || null;
|
|
269
|
+
this.responseModel = (event.message.model as string) || null;
|
|
270
|
+
const usage = event.message.usage as Record<string, number> | undefined;
|
|
271
|
+
if (usage) this.inputTokens = usage.input_tokens || 0;
|
|
272
|
+
} else if (event.type === "content_block_start" && event.content_block) {
|
|
273
|
+
const bt = (event.content_block.type as string) || "";
|
|
274
|
+
this.currentBlock = { type: bt };
|
|
275
|
+
if (bt === "thinking") {
|
|
276
|
+
this.currentBlock.thinking = "";
|
|
277
|
+
this.currentBlock.signature = "";
|
|
278
|
+
} else if (bt === "text") {
|
|
279
|
+
this.currentBlock.text = "";
|
|
280
|
+
}
|
|
281
|
+
} else if (event.type === "content_block_delta" && event.delta) {
|
|
282
|
+
if (!this.currentBlock) return;
|
|
283
|
+
const dt = (event.delta.type as string) || "";
|
|
284
|
+
if (dt === "thinking_delta") {
|
|
285
|
+
this.currentBlock.thinking =
|
|
286
|
+
((this.currentBlock.thinking as string) || "") +
|
|
287
|
+
((event.delta.thinking as string) || "");
|
|
288
|
+
} else if (dt === "text_delta") {
|
|
289
|
+
this.currentBlock.text =
|
|
290
|
+
((this.currentBlock.text as string) || "") +
|
|
291
|
+
((event.delta.text as string) || "");
|
|
292
|
+
} else if (dt === "signature_delta") {
|
|
293
|
+
this.currentBlock.signature =
|
|
294
|
+
((this.currentBlock.signature as string) || "") +
|
|
295
|
+
((event.delta.signature as string) || "");
|
|
296
|
+
}
|
|
297
|
+
} else if (event.type === "content_block_stop") {
|
|
298
|
+
if (this.currentBlock) {
|
|
299
|
+
this.blocks.push(this.currentBlock);
|
|
300
|
+
this.currentBlock = null;
|
|
301
|
+
}
|
|
302
|
+
} else if (event.type === "message_delta") {
|
|
303
|
+
const usage = (event as unknown as Record<string, unknown>).usage as
|
|
304
|
+
| Record<string, number>
|
|
305
|
+
| undefined;
|
|
306
|
+
if (usage) this.outputTokens = usage.output_tokens || 0;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private finalize(): void {
|
|
311
|
+
if (this.finalized) return;
|
|
312
|
+
this.finalized = true;
|
|
313
|
+
|
|
314
|
+
if (this.blocks.length > 0) {
|
|
315
|
+
const outputMsgs = convertAnthropicOutput(this.blocks);
|
|
316
|
+
if (outputMsgs.length > 0) {
|
|
317
|
+
this.span.setAttribute(
|
|
318
|
+
"gen_ai.output.messages",
|
|
319
|
+
serializeMessages(outputMsgs),
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
if (this.responseId)
|
|
324
|
+
this.span.setAttribute("gen_ai.response.id", this.responseId);
|
|
325
|
+
if (this.responseModel)
|
|
326
|
+
this.span.setAttribute("gen_ai.response.model", this.responseModel);
|
|
327
|
+
if (this.inputTokens)
|
|
328
|
+
this.span.setAttribute("gen_ai.usage.input_tokens", this.inputTokens);
|
|
329
|
+
if (this.outputTokens)
|
|
330
|
+
this.span.setAttribute("gen_ai.usage.output_tokens", this.outputTokens);
|
|
331
|
+
this.span.setStatus({ code: SpanStatusCode.OK });
|
|
332
|
+
this.span.end();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ---------------------------------------------------------------------------
|
|
337
|
+
// Public API
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Traced wrapper around `client.messages.create(kwargs)`.
|
|
342
|
+
* Creates a gen_ai span with full Anthropic response including thinking blocks.
|
|
343
|
+
*/
|
|
344
|
+
export async function tracedMessagesCreate(
|
|
345
|
+
tracer: Tracer,
|
|
346
|
+
client: { messages: { create: (...args: unknown[]) => Promise<unknown> } },
|
|
347
|
+
kwargs: Record<string, unknown>,
|
|
348
|
+
): Promise<unknown> {
|
|
349
|
+
const { span } = startSpan(tracer, (kwargs.model as string) || "unknown");
|
|
350
|
+
try {
|
|
351
|
+
setRequestAttrs(span, kwargs);
|
|
352
|
+
const response = (await client.messages.create(kwargs)) as Record<
|
|
353
|
+
string,
|
|
354
|
+
unknown
|
|
355
|
+
>;
|
|
356
|
+
setResponseAttrs(span, response);
|
|
357
|
+
return response;
|
|
358
|
+
} catch (e) {
|
|
359
|
+
span.setStatus({
|
|
360
|
+
code: SpanStatusCode.ERROR,
|
|
361
|
+
message: String(e),
|
|
362
|
+
});
|
|
363
|
+
throw e;
|
|
364
|
+
} finally {
|
|
365
|
+
span.end();
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Auto-instrumentor that patches `Anthropic.messages.create` to add tracing.
|
|
371
|
+
*
|
|
372
|
+
* Captures all content blocks including thinking (extended thinking) with
|
|
373
|
+
* signatures. Supports both non-streaming and streaming calls.
|
|
374
|
+
*/
|
|
375
|
+
/**
|
|
376
|
+
* Auto-instrumentor that wraps an Anthropic client instance to add tracing.
|
|
377
|
+
*
|
|
378
|
+
* Captures all content blocks including thinking (extended thinking) with
|
|
379
|
+
* signatures. Supports both non-streaming and streaming calls.
|
|
380
|
+
*/
|
|
381
|
+
export class AnthropicInstrumentor {
|
|
382
|
+
private tracer: Tracer | null = null;
|
|
383
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
384
|
+
private patchedClients: Array<{ client: any; originalCreate: any }> = [];
|
|
385
|
+
|
|
386
|
+
instrument(opts: {
|
|
387
|
+
tracerProvider?: BasicTracerProvider;
|
|
388
|
+
/** The Anthropic client instance to instrument. */
|
|
389
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
390
|
+
client: any;
|
|
391
|
+
}): void {
|
|
392
|
+
const { client } = opts;
|
|
393
|
+
if (!client?.messages?.create) {
|
|
394
|
+
throw new Error(
|
|
395
|
+
"Invalid client — pass an Anthropic client instance (new Anthropic())",
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const provider = opts.tracerProvider ?? trace.getTracerProvider();
|
|
400
|
+
this.tracer = (provider as BasicTracerProvider).getTracer(
|
|
401
|
+
"introspection-anthropic",
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
const origCreate = client.messages.create.bind(client.messages);
|
|
405
|
+
this.patchedClients.push({ client, originalCreate: origCreate });
|
|
406
|
+
const tracer = this.tracer!;
|
|
407
|
+
|
|
408
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
409
|
+
client.messages.create = function (...args: any[]) {
|
|
410
|
+
const kwargs = args[0] as Record<string, unknown>;
|
|
411
|
+
const isStream = kwargs?.stream === true;
|
|
412
|
+
|
|
413
|
+
const { span } = startSpan(
|
|
414
|
+
tracer,
|
|
415
|
+
(kwargs?.model as string) || "unknown",
|
|
416
|
+
);
|
|
417
|
+
setRequestAttrs(span, kwargs || {});
|
|
418
|
+
|
|
419
|
+
if (isStream) {
|
|
420
|
+
const streamPromise = origCreate(...args) as Promise<
|
|
421
|
+
AsyncIterable<StreamEvent>
|
|
422
|
+
>;
|
|
423
|
+
return streamPromise.then(
|
|
424
|
+
(stream: AsyncIterable<StreamEvent>) =>
|
|
425
|
+
new TracedStream(stream, span),
|
|
426
|
+
(err: Error) => {
|
|
427
|
+
span.setStatus({
|
|
428
|
+
code: SpanStatusCode.ERROR,
|
|
429
|
+
message: String(err),
|
|
430
|
+
});
|
|
431
|
+
span.end();
|
|
432
|
+
throw err;
|
|
433
|
+
},
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const resultPromise = origCreate(...args) as Promise<
|
|
438
|
+
Record<string, unknown>
|
|
439
|
+
>;
|
|
440
|
+
return resultPromise.then(
|
|
441
|
+
(response: Record<string, unknown>) => {
|
|
442
|
+
setResponseAttrs(span, response);
|
|
443
|
+
span.end();
|
|
444
|
+
return response;
|
|
445
|
+
},
|
|
446
|
+
(err: Error) => {
|
|
447
|
+
span.setStatus({
|
|
448
|
+
code: SpanStatusCode.ERROR,
|
|
449
|
+
message: String(err),
|
|
450
|
+
});
|
|
451
|
+
span.end();
|
|
452
|
+
throw err;
|
|
453
|
+
},
|
|
454
|
+
);
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
uninstrument(): void {
|
|
459
|
+
for (const { client, originalCreate } of this.patchedClients) {
|
|
460
|
+
client.messages.create = originalCreate;
|
|
461
|
+
}
|
|
462
|
+
this.patchedClients = [];
|
|
463
|
+
this.tracer = null;
|
|
464
|
+
}
|
|
465
|
+
}
|