@telemetry-dev/anthropic 0.1.0 → 0.1.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.
- package/dist/index.mjs +59 -52
- package/package.json +4 -4
- package/src/index.ts +174 -214
package/dist/index.mjs
CHANGED
|
@@ -6,22 +6,27 @@ const WRAPPED = Symbol("telemetry.dev.anthropic.wrapped");
|
|
|
6
6
|
const WRAPPED_ORIGINAL = Symbol("telemetry.dev.anthropic.original");
|
|
7
7
|
const wrappedClients = /* @__PURE__ */ new WeakSet();
|
|
8
8
|
function asRecord(value) {
|
|
9
|
-
|
|
9
|
+
if (value === null || value === void 0 || value instanceof Function) return void 0;
|
|
10
|
+
return Object(value) === value ? value : void 0;
|
|
10
11
|
}
|
|
11
12
|
function readString(value) {
|
|
12
|
-
return
|
|
13
|
+
return String(value) === value ? value : void 0;
|
|
13
14
|
}
|
|
14
15
|
function readNumber(value) {
|
|
15
|
-
return
|
|
16
|
+
return Number(value) === value ? value : void 0;
|
|
16
17
|
}
|
|
17
18
|
function compactUsage(usage) {
|
|
18
19
|
if (!usage) return void 0;
|
|
19
20
|
return Object.values(usage).some((value) => value !== void 0) ? usage : void 0;
|
|
20
21
|
}
|
|
21
22
|
function stopSequences(value) {
|
|
22
|
-
|
|
23
|
+
const single = readString(value);
|
|
24
|
+
if (single !== void 0) return [single];
|
|
23
25
|
if (!Array.isArray(value)) return void 0;
|
|
24
|
-
const strings = value.
|
|
26
|
+
const strings = value.flatMap((item) => {
|
|
27
|
+
const string = readString(item);
|
|
28
|
+
return string === void 0 ? [] : [string];
|
|
29
|
+
});
|
|
25
30
|
return strings.length > 0 ? strings : void 0;
|
|
26
31
|
}
|
|
27
32
|
function messagesRequest(body) {
|
|
@@ -47,14 +52,14 @@ function messagesRequest(body) {
|
|
|
47
52
|
};
|
|
48
53
|
}
|
|
49
54
|
function messagesUsage(usage) {
|
|
50
|
-
const
|
|
51
|
-
const
|
|
55
|
+
const record = asRecord(usage);
|
|
56
|
+
const details = asRecord(record?.output_tokens_details);
|
|
52
57
|
return compactUsage({
|
|
53
|
-
inputTokens: readNumber(
|
|
54
|
-
outputTokens: readNumber(
|
|
55
|
-
cacheReadInputTokens: readNumber(
|
|
56
|
-
cacheCreationInputTokens: readNumber(
|
|
57
|
-
reasoningOutputTokens: readNumber(
|
|
58
|
+
inputTokens: readNumber(record?.input_tokens),
|
|
59
|
+
outputTokens: readNumber(record?.output_tokens),
|
|
60
|
+
cacheReadInputTokens: readNumber(record?.cache_read_input_tokens),
|
|
61
|
+
cacheCreationInputTokens: readNumber(record?.cache_creation_input_tokens),
|
|
62
|
+
reasoningOutputTokens: readNumber(details?.thinking_tokens)
|
|
58
63
|
});
|
|
59
64
|
}
|
|
60
65
|
function mergeUsage(current, incoming) {
|
|
@@ -68,23 +73,23 @@ function mergeUsage(current, incoming) {
|
|
|
68
73
|
});
|
|
69
74
|
}
|
|
70
75
|
function messagesResponse(response) {
|
|
71
|
-
const
|
|
72
|
-
const role = readString(
|
|
76
|
+
const record = asRecord(response) ?? {};
|
|
77
|
+
const role = readString(record.role) ?? "assistant";
|
|
73
78
|
return {
|
|
74
|
-
responseModel: readString(
|
|
75
|
-
responseId: readString(
|
|
76
|
-
finishReason: readString(
|
|
77
|
-
output:
|
|
79
|
+
responseModel: readString(record.model),
|
|
80
|
+
responseId: readString(record.id),
|
|
81
|
+
finishReason: readString(record.stop_reason),
|
|
82
|
+
output: record.content !== void 0 ? [{
|
|
78
83
|
role,
|
|
79
|
-
content:
|
|
84
|
+
content: record.content
|
|
80
85
|
}] : void 0,
|
|
81
|
-
usage: messagesUsage(
|
|
86
|
+
usage: messagesUsage(record.usage)
|
|
82
87
|
};
|
|
83
88
|
}
|
|
84
89
|
function constructorName(value) {
|
|
85
|
-
if (value === null ||
|
|
86
|
-
const ctor = value.constructor;
|
|
87
|
-
return
|
|
90
|
+
if (value === null || value === void 0) return void 0;
|
|
91
|
+
const ctor = Object(value).constructor;
|
|
92
|
+
return ctor instanceof Function ? ctor.name : void 0;
|
|
88
93
|
}
|
|
89
94
|
function providerForClient(client) {
|
|
90
95
|
switch (constructorName(client)) {
|
|
@@ -99,7 +104,7 @@ function providerForResource(resource) {
|
|
|
99
104
|
return providerForClient(asRecord(resource)?._client);
|
|
100
105
|
}
|
|
101
106
|
function isWrapped(fn) {
|
|
102
|
-
if (
|
|
107
|
+
if (!(fn instanceof Function)) return false;
|
|
103
108
|
return fn[WRAPPED] === true;
|
|
104
109
|
}
|
|
105
110
|
function markWrapped(fn, original) {
|
|
@@ -116,11 +121,10 @@ function endOnce(span) {
|
|
|
116
121
|
};
|
|
117
122
|
}
|
|
118
123
|
function mapRawResponse(response) {
|
|
124
|
+
const fields = { attributes: { "http.response.status_code": response.status } };
|
|
119
125
|
const requestId = response.headers.get("request-id");
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
attributes: { "http.response.status_code": response.status }
|
|
123
|
-
};
|
|
126
|
+
if (requestId) fields.responseId = requestId;
|
|
127
|
+
return fields;
|
|
124
128
|
}
|
|
125
129
|
function finalizeParsedValue(value, ctx) {
|
|
126
130
|
if (ctx.streaming) {
|
|
@@ -139,14 +143,14 @@ function makeTracedPromise(inner, ctx) {
|
|
|
139
143
|
const originalAsResponse = source.asResponse?.bind(source);
|
|
140
144
|
const originalThenUnwrap = source._thenUnwrap?.bind(source);
|
|
141
145
|
const handleError = (error) => {
|
|
142
|
-
ctx.end({ error });
|
|
146
|
+
ctx.end({ error: error instanceof Error ? error : new Error(String(error)) });
|
|
143
147
|
throw error;
|
|
144
148
|
};
|
|
145
149
|
const onParsed = (value) => finalizeParsedValue(value, ctx);
|
|
146
150
|
const parsed = () => originalThen(onParsed, handleError);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
151
|
+
let traced;
|
|
152
|
+
if (constructorName(source) === "APIPromise") traced = source;
|
|
153
|
+
else traced = new Promise((resolve, reject) => parsed().then(resolve, reject));
|
|
150
154
|
if (traced === source) {
|
|
151
155
|
traced.then = ((onfulfilled, onrejected) => parsed().then(onfulfilled, onrejected));
|
|
152
156
|
traced.catch = ((onrejected) => parsed().catch(onrejected));
|
|
@@ -169,7 +173,9 @@ function makeTracedPromise(inner, ctx) {
|
|
|
169
173
|
};
|
|
170
174
|
traced._thenUnwrap = (transform) => {
|
|
171
175
|
if (!originalThenUnwrap) return rejectMissing("_thenUnwrap");
|
|
172
|
-
return makeTracedPromise(originalThenUnwrap(
|
|
176
|
+
return makeTracedPromise(originalThenUnwrap((data, ...args) => {
|
|
177
|
+
return transform(data, ...args);
|
|
178
|
+
}), ctx);
|
|
173
179
|
};
|
|
174
180
|
return traced;
|
|
175
181
|
}
|
|
@@ -301,28 +307,30 @@ function recordContentBlockDelta(event, state) {
|
|
|
301
307
|
if (deltaType === "signature_delta" && delta.signature !== void 0) data.signature = delta.signature;
|
|
302
308
|
}
|
|
303
309
|
function recordStreamEvent(event, state) {
|
|
304
|
-
const
|
|
305
|
-
const type = readString(
|
|
310
|
+
const record = asRecord(event) ?? {};
|
|
311
|
+
const type = readString(record.type);
|
|
306
312
|
const fields = {};
|
|
307
313
|
if (type === "message_start") {
|
|
308
|
-
const message = asRecord(
|
|
314
|
+
const message = asRecord(record.message) ?? {};
|
|
309
315
|
fields.responseId = readString(message.id);
|
|
310
316
|
fields.responseModel = readString(message.model);
|
|
311
317
|
state.usage = mergeUsage(state.usage, messagesUsage(message.usage));
|
|
312
318
|
}
|
|
313
|
-
if (type === "content_block_start") recordContentBlockStart(
|
|
314
|
-
if (type === "content_block_delta") recordContentBlockDelta(
|
|
319
|
+
if (type === "content_block_start") recordContentBlockStart(record, state);
|
|
320
|
+
if (type === "content_block_delta") recordContentBlockDelta(record, state);
|
|
315
321
|
if (type === "message_delta") {
|
|
316
|
-
state.finishReason = readString((asRecord(
|
|
317
|
-
state.usage = mergeUsage(state.usage, messagesUsage(
|
|
322
|
+
state.finishReason = readString((asRecord(record.delta) ?? {}).stop_reason) ?? state.finishReason;
|
|
323
|
+
state.usage = mergeUsage(state.usage, messagesUsage(record.usage));
|
|
318
324
|
}
|
|
319
325
|
return fields;
|
|
320
326
|
}
|
|
321
327
|
function isStreamLike(value) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const
|
|
325
|
-
|
|
328
|
+
const stream = asRecord(value);
|
|
329
|
+
if (!stream) return false;
|
|
330
|
+
const controller = asRecord(stream.controller);
|
|
331
|
+
const signal = asRecord(controller?.signal);
|
|
332
|
+
if (!controller || !(controller.abort instanceof Function) || !signal || typeof signal.aborted !== "boolean" || !(signal.addEventListener instanceof Function)) return false;
|
|
333
|
+
return stream[Symbol.asyncIterator] instanceof Function;
|
|
326
334
|
}
|
|
327
335
|
function createObservedMessagesStream(source, span, startedAt, end) {
|
|
328
336
|
const state = { blocks: /* @__PURE__ */ new Map() };
|
|
@@ -332,7 +340,7 @@ function createObservedMessagesStream(source, span, startedAt, end) {
|
|
|
332
340
|
if (consumed) return;
|
|
333
341
|
end({
|
|
334
342
|
...streamPartialFields(state),
|
|
335
|
-
error: signal.reason
|
|
343
|
+
error: signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("Request aborted")
|
|
336
344
|
});
|
|
337
345
|
};
|
|
338
346
|
if (signal.aborted) endAborted();
|
|
@@ -350,10 +358,9 @@ function createObservedMessagesStream(source, span, startedAt, end) {
|
|
|
350
358
|
terminalError = error;
|
|
351
359
|
throw error;
|
|
352
360
|
} finally {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
});
|
|
361
|
+
const fields = streamPartialFields(state);
|
|
362
|
+
if (terminalError !== void 0) fields.error = terminalError instanceof Error ? terminalError : new Error(String(terminalError));
|
|
363
|
+
end(fields);
|
|
357
364
|
}
|
|
358
365
|
}
|
|
359
366
|
return new Stream(() => iterator(), source.controller);
|
|
@@ -383,7 +390,7 @@ function wrapCreate(original, mapRequest, mapResponse, provider) {
|
|
|
383
390
|
mapResponse
|
|
384
391
|
});
|
|
385
392
|
} catch (error) {
|
|
386
|
-
end({ error });
|
|
393
|
+
end({ error: error instanceof Error ? error : new Error(String(error)) });
|
|
387
394
|
throw error;
|
|
388
395
|
}
|
|
389
396
|
};
|
|
@@ -393,13 +400,13 @@ function patchInstanceMethod(target, key, request, response, provider) {
|
|
|
393
400
|
const current = target[key];
|
|
394
401
|
if (isWrapped(current) && Object.hasOwn(target, key)) return;
|
|
395
402
|
const original = isWrapped(current) ? current[WRAPPED_ORIGINAL] : current;
|
|
396
|
-
if (
|
|
403
|
+
if (!(original instanceof Function)) return;
|
|
397
404
|
target[key] = wrapCreate(original.bind(target), request, response, () => provider);
|
|
398
405
|
}
|
|
399
406
|
function patchPrototype(klass, key, request, response) {
|
|
400
407
|
const prototype = klass.prototype;
|
|
401
408
|
const original = prototype[key];
|
|
402
|
-
if (
|
|
409
|
+
if (!(original instanceof Function) || isWrapped(original)) return () => {};
|
|
403
410
|
prototype[key] = wrapCreate(original, request, response, providerForResource);
|
|
404
411
|
return () => {
|
|
405
412
|
prototype[key] = original;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telemetry-dev/anthropic",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Anthropic SDK instrumentation for telemetry.dev: wraps the Messages API and emits OpenTelemetry GenAI spans.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"anthropic",
|
|
@@ -40,15 +40,15 @@
|
|
|
40
40
|
"@opentelemetry/sdk-logs": "^0.218.0",
|
|
41
41
|
"@opentelemetry/sdk-metrics": "^2.7.1",
|
|
42
42
|
"@opentelemetry/sdk-trace-base": "^2.7.1",
|
|
43
|
+
"@telemetry-dev/sdk": "0.1.1",
|
|
43
44
|
"@types/node": "^24",
|
|
44
45
|
"typescript": "^5",
|
|
45
46
|
"vite-plus": "0.1.20",
|
|
46
|
-
"vitest": "npm:@voidzero-dev/vite-plus-test@0.1.20"
|
|
47
|
-
"@telemetry-dev/sdk": "0.1.0"
|
|
47
|
+
"vitest": "npm:@voidzero-dev/vite-plus-test@0.1.20"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
50
|
"@anthropic-ai/sdk": ">=0.110.0 <1",
|
|
51
|
-
"@telemetry-dev/sdk": "^0.1.
|
|
51
|
+
"@telemetry-dev/sdk": "^0.1.1"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
54
|
"build": "pnpm exec vp pack",
|
package/src/index.ts
CHANGED
|
@@ -13,64 +13,70 @@ const WRAPPED = Symbol("telemetry.dev.anthropic.wrapped");
|
|
|
13
13
|
const WRAPPED_ORIGINAL = Symbol("telemetry.dev.anthropic.original");
|
|
14
14
|
const wrappedClients = new WeakSet<object>();
|
|
15
15
|
|
|
16
|
-
type
|
|
17
|
-
|
|
16
|
+
type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
|
|
17
|
+
interface OpaqueValue {}
|
|
18
|
+
type Value = string | number | boolean | null | undefined | OpaqueValue;
|
|
19
|
+
interface ValueRecord {
|
|
20
|
+
[key: string]: JsonValue | undefined;
|
|
21
|
+
[key: symbol]: JsonValue | undefined;
|
|
22
|
+
}
|
|
23
|
+
interface CallableRecord {
|
|
24
|
+
[key: string]: JsonValue | AnyFunction | undefined;
|
|
25
|
+
}
|
|
26
|
+
type AnyFunction = (...args: never[]) => OpaqueValue;
|
|
27
|
+
type WrappedFunction = AnyFunction & {
|
|
18
28
|
[WRAPPED]?: true;
|
|
19
|
-
[WRAPPED_ORIGINAL]?:
|
|
29
|
+
[WRAPPED_ORIGINAL]?: AnyFunction;
|
|
20
30
|
};
|
|
21
|
-
type ProviderResolver = (resource:
|
|
31
|
+
type ProviderResolver = (resource: Value) => string;
|
|
22
32
|
|
|
23
33
|
interface AnthropicClient {
|
|
24
34
|
messages: object;
|
|
25
35
|
}
|
|
26
|
-
|
|
27
36
|
interface RequestMapping {
|
|
28
37
|
name: string;
|
|
29
38
|
fields: StartSpanOptions;
|
|
30
39
|
}
|
|
31
|
-
|
|
32
40
|
interface WrappedResponse<T> {
|
|
33
41
|
data: T;
|
|
34
42
|
response: Response;
|
|
35
43
|
request_id: string | null;
|
|
36
44
|
}
|
|
37
|
-
|
|
38
45
|
interface ToolBlockState {
|
|
39
|
-
data:
|
|
46
|
+
data: ValueRecord;
|
|
40
47
|
inputJson: string;
|
|
41
48
|
}
|
|
42
|
-
|
|
43
49
|
interface StreamState {
|
|
44
|
-
blocks: Map<number,
|
|
50
|
+
blocks: Map<number, ValueRecord | ToolBlockState>;
|
|
45
51
|
usage?: SpanFields["usage"];
|
|
46
52
|
finishReason?: string;
|
|
47
53
|
}
|
|
48
54
|
|
|
49
|
-
function asRecord(value:
|
|
50
|
-
|
|
55
|
+
function asRecord<T>(value: T): (T & ValueRecord) | undefined {
|
|
56
|
+
if (value === null || value === undefined || value instanceof Function) return undefined;
|
|
57
|
+
return Object(value) === value ? (value as T & ValueRecord) : undefined;
|
|
51
58
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return typeof value === "string" ? value : undefined;
|
|
59
|
+
function readString<T>(value: T): string | undefined {
|
|
60
|
+
return String(value) === value ? (value as string) : undefined;
|
|
55
61
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return typeof value === "number" ? value : undefined;
|
|
62
|
+
function readNumber<T>(value: T): number | undefined {
|
|
63
|
+
return Number(value) === value ? (value as number) : undefined;
|
|
59
64
|
}
|
|
60
|
-
|
|
61
65
|
function compactUsage(usage: SpanFields["usage"]): SpanFields["usage"] {
|
|
62
66
|
if (!usage) return undefined;
|
|
63
67
|
return Object.values(usage).some((value) => value !== undefined) ? usage : undefined;
|
|
64
68
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
if (
|
|
69
|
+
function stopSequences<T>(value: T): string[] | undefined {
|
|
70
|
+
const single = readString(value);
|
|
71
|
+
if (single !== undefined) return [single];
|
|
68
72
|
if (!Array.isArray(value)) return undefined;
|
|
69
|
-
const strings = value.
|
|
73
|
+
const strings = value.flatMap((item) => {
|
|
74
|
+
const string = readString(item);
|
|
75
|
+
return string === undefined ? [] : [string];
|
|
76
|
+
});
|
|
70
77
|
return strings.length > 0 ? strings : undefined;
|
|
71
78
|
}
|
|
72
|
-
|
|
73
|
-
function messagesRequest(body: UnknownRecord): RequestMapping {
|
|
79
|
+
function messagesRequest(body: ValueRecord): RequestMapping {
|
|
74
80
|
const model = readString(body.model);
|
|
75
81
|
const input =
|
|
76
82
|
body.tools !== undefined || body.tool_choice !== undefined
|
|
@@ -91,19 +97,17 @@ function messagesRequest(body: UnknownRecord): RequestMapping {
|
|
|
91
97
|
},
|
|
92
98
|
};
|
|
93
99
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
const outputDetails = asRecord(u?.output_tokens_details);
|
|
100
|
+
function messagesUsage<T>(usage: T): SpanFields["usage"] {
|
|
101
|
+
const record = asRecord(usage);
|
|
102
|
+
const details = asRecord(record?.output_tokens_details);
|
|
98
103
|
return compactUsage({
|
|
99
|
-
inputTokens: readNumber(
|
|
100
|
-
outputTokens: readNumber(
|
|
101
|
-
cacheReadInputTokens: readNumber(
|
|
102
|
-
cacheCreationInputTokens: readNumber(
|
|
103
|
-
reasoningOutputTokens: readNumber(
|
|
104
|
+
inputTokens: readNumber(record?.input_tokens),
|
|
105
|
+
outputTokens: readNumber(record?.output_tokens),
|
|
106
|
+
cacheReadInputTokens: readNumber(record?.cache_read_input_tokens),
|
|
107
|
+
cacheCreationInputTokens: readNumber(record?.cache_creation_input_tokens),
|
|
108
|
+
reasoningOutputTokens: readNumber(details?.thinking_tokens),
|
|
104
109
|
});
|
|
105
110
|
}
|
|
106
|
-
|
|
107
111
|
function mergeUsage(
|
|
108
112
|
current: SpanFields["usage"],
|
|
109
113
|
incoming: SpanFields["usage"],
|
|
@@ -118,28 +122,23 @@ function mergeUsage(
|
|
|
118
122
|
reasoningOutputTokens: incoming.reasoningOutputTokens ?? current?.reasoningOutputTokens,
|
|
119
123
|
});
|
|
120
124
|
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
const role = readString(r.role) ?? "assistant";
|
|
125
|
+
function messagesResponse<T>(response: T): SpanFields {
|
|
126
|
+
const record: ValueRecord = asRecord(response) ?? {};
|
|
127
|
+
const role = readString(record.role) ?? "assistant";
|
|
125
128
|
return {
|
|
126
|
-
responseModel: readString(
|
|
127
|
-
responseId: readString(
|
|
128
|
-
finishReason: readString(
|
|
129
|
-
output:
|
|
130
|
-
usage: messagesUsage(
|
|
129
|
+
responseModel: readString(record.model),
|
|
130
|
+
responseId: readString(record.id),
|
|
131
|
+
finishReason: readString(record.stop_reason),
|
|
132
|
+
output: record.content !== undefined ? [{ role, content: record.content }] : undefined,
|
|
133
|
+
usage: messagesUsage(record.usage),
|
|
131
134
|
};
|
|
132
135
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
const ctor = value.constructor;
|
|
139
|
-
return typeof ctor === "function" ? ctor.name : undefined;
|
|
136
|
+
function constructorName<T>(value: T): string | undefined {
|
|
137
|
+
if (value === null || value === undefined) return undefined;
|
|
138
|
+
const ctor = Object(value).constructor;
|
|
139
|
+
return ctor instanceof Function ? ctor.name : undefined;
|
|
140
140
|
}
|
|
141
|
-
|
|
142
|
-
function providerForClient(client: unknown): string {
|
|
141
|
+
function providerForClient<T>(client: T): string {
|
|
143
142
|
switch (constructorName(client)) {
|
|
144
143
|
case "AnthropicBedrock":
|
|
145
144
|
case "AsyncAnthropicBedrock":
|
|
@@ -151,23 +150,18 @@ function providerForClient(client: unknown): string {
|
|
|
151
150
|
return "anthropic";
|
|
152
151
|
}
|
|
153
152
|
}
|
|
154
|
-
|
|
155
|
-
function providerForResource(resource: unknown): string {
|
|
153
|
+
function providerForResource<T>(resource: T): string {
|
|
156
154
|
return providerForClient(asRecord(resource)?._client);
|
|
157
155
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const wrapped = fn as WrappedFunction;
|
|
162
|
-
return wrapped[WRAPPED] === true;
|
|
156
|
+
function isWrapped<T>(fn: T): fn is T & WrappedFunction {
|
|
157
|
+
if (!(fn instanceof Function)) return false;
|
|
158
|
+
return (fn as WrappedFunction)[WRAPPED] === true;
|
|
163
159
|
}
|
|
164
|
-
|
|
165
|
-
function markWrapped<T extends WrappedFunction>(fn: T, original: (...args: never[]) => unknown): T {
|
|
160
|
+
function markWrapped<T extends WrappedFunction>(fn: T, original: AnyFunction): T {
|
|
166
161
|
Object.defineProperty(fn, WRAPPED, { value: true });
|
|
167
162
|
Object.defineProperty(fn, WRAPPED_ORIGINAL, { value: original });
|
|
168
163
|
return fn;
|
|
169
164
|
}
|
|
170
|
-
|
|
171
165
|
function endOnce(span: SpanHandle): (fields?: SpanFields) => void {
|
|
172
166
|
let ended = false;
|
|
173
167
|
return (fields?: SpanFields) => {
|
|
@@ -180,39 +174,32 @@ function endOnce(span: SpanHandle): (fields?: SpanFields) => void {
|
|
|
180
174
|
type TracedResult<T> = Promise<T> & {
|
|
181
175
|
asResponse(): Promise<Response>;
|
|
182
176
|
withResponse(): Promise<WrappedResponse<T>>;
|
|
183
|
-
_thenUnwrap<U>(transform: (data: T, ...args:
|
|
177
|
+
_thenUnwrap<U>(transform: (data: T, ...args: Value[]) => U): TracedResult<U>;
|
|
184
178
|
};
|
|
185
|
-
|
|
186
179
|
interface TracePromiseContext {
|
|
187
180
|
end: (fields?: SpanFields) => void;
|
|
188
181
|
span: SpanHandle;
|
|
189
182
|
startedAt: number;
|
|
190
183
|
streaming: boolean;
|
|
191
|
-
mapResponse: (response:
|
|
184
|
+
mapResponse: (response: Value) => SpanFields;
|
|
192
185
|
}
|
|
193
|
-
|
|
194
186
|
interface StreamLike<T> extends AsyncIterable<T> {
|
|
195
187
|
controller: AbortController;
|
|
196
188
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
finally: Promise<unknown>["finally"];
|
|
189
|
+
interface InnerAPIPromise {
|
|
190
|
+
then: Promise<Value>["then"];
|
|
191
|
+
catch: Promise<Value>["catch"];
|
|
192
|
+
finally: Promise<Value>["finally"];
|
|
202
193
|
asResponse?: () => Promise<Response>;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
};
|
|
206
|
-
|
|
194
|
+
_thenUnwrap?: <U>(transform: (data: Value, ...args: Value[]) => U) => InnerAPIPromise;
|
|
195
|
+
}
|
|
207
196
|
function mapRawResponse(response: Response): SpanFields {
|
|
197
|
+
const fields: SpanFields = { attributes: { "http.response.status_code": response.status } };
|
|
208
198
|
const requestId = response.headers.get("request-id");
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
attributes: { "http.response.status_code": response.status },
|
|
212
|
-
};
|
|
199
|
+
if (requestId) fields.responseId = requestId;
|
|
200
|
+
return fields;
|
|
213
201
|
}
|
|
214
|
-
|
|
215
|
-
function finalizeParsedValue(value: unknown, ctx: TracePromiseContext): unknown {
|
|
202
|
+
function finalizeParsedValue(value: Value, ctx: TracePromiseContext): Value {
|
|
216
203
|
if (ctx.streaming) {
|
|
217
204
|
const wrapped = wrapStream(value, ctx.span, ctx.startedAt, ctx.end);
|
|
218
205
|
if (wrapped !== value) return wrapped;
|
|
@@ -220,28 +207,26 @@ function finalizeParsedValue(value: unknown, ctx: TracePromiseContext): unknown
|
|
|
220
207
|
ctx.end(ctx.mapResponse(value));
|
|
221
208
|
return value;
|
|
222
209
|
}
|
|
223
|
-
|
|
224
210
|
function rejectMissing(method: string): Promise<never> {
|
|
225
211
|
return Promise.reject(new TypeError(`wrapped result has no ${method}`));
|
|
226
212
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const source = inner as InnerAPIPromise;
|
|
213
|
+
function makeTracedPromise<T>(inner: InnerAPIPromise, ctx: TracePromiseContext): TracedResult<T> {
|
|
214
|
+
const source = inner;
|
|
230
215
|
const originalThen = source.then.bind(source);
|
|
231
216
|
const originalAsResponse = source.asResponse?.bind(source);
|
|
232
217
|
const originalThenUnwrap = source._thenUnwrap?.bind(source);
|
|
233
|
-
const handleError = (error:
|
|
234
|
-
ctx.end({ error });
|
|
218
|
+
const handleError = (error: Value): never => {
|
|
219
|
+
ctx.end({ error: error instanceof Error ? error : new Error(String(error)) });
|
|
235
220
|
throw error;
|
|
236
221
|
};
|
|
237
|
-
const onParsed = (value:
|
|
222
|
+
const onParsed = (value: Value) => finalizeParsedValue(value, ctx) as T;
|
|
238
223
|
const parsed = () => originalThen(onParsed, handleError);
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
224
|
+
let traced: TracedResult<T>;
|
|
225
|
+
if (constructorName(source) === "APIPromise") {
|
|
226
|
+
traced = source as TracedResult<T>;
|
|
227
|
+
} else {
|
|
228
|
+
traced = new Promise<T>((resolve, reject) => parsed().then(resolve, reject)) as TracedResult<T>;
|
|
229
|
+
}
|
|
245
230
|
if (traced === source) {
|
|
246
231
|
// oxlint-disable-next-line unicorn/no-thenable -- preserve Anthropic APIPromise subclass contract.
|
|
247
232
|
traced.then = ((onfulfilled, onrejected) =>
|
|
@@ -259,28 +244,27 @@ function makeTracedPromise<T>(inner: unknown, ctx: TracePromiseContext): TracedR
|
|
|
259
244
|
traced.withResponse = () => {
|
|
260
245
|
if (!originalAsResponse) return rejectMissing("withResponse");
|
|
261
246
|
return Promise.all([parsed(), originalAsResponse()]).then(
|
|
262
|
-
([data, response]) => ({
|
|
263
|
-
data,
|
|
264
|
-
response,
|
|
265
|
-
request_id: response.headers.get("request-id"),
|
|
266
|
-
}),
|
|
247
|
+
([data, response]) => ({ data, response, request_id: response.headers.get("request-id") }),
|
|
267
248
|
handleError,
|
|
268
249
|
);
|
|
269
250
|
};
|
|
270
|
-
traced._thenUnwrap = <U>(transform: (data: T, ...args:
|
|
271
|
-
if (!originalThenUnwrap)
|
|
251
|
+
traced._thenUnwrap = <U>(transform: (data: T, ...args: Value[]) => U): TracedResult<U> => {
|
|
252
|
+
if (!originalThenUnwrap) {
|
|
253
|
+
return rejectMissing("_thenUnwrap") as Promise<never> & TracedResult<U>;
|
|
254
|
+
}
|
|
272
255
|
return makeTracedPromise(
|
|
273
|
-
originalThenUnwrap(
|
|
256
|
+
originalThenUnwrap((data, ...args) => {
|
|
257
|
+
return transform(data as T, ...args);
|
|
258
|
+
}),
|
|
274
259
|
ctx,
|
|
275
260
|
);
|
|
276
261
|
};
|
|
277
262
|
return traced;
|
|
278
263
|
}
|
|
279
264
|
|
|
280
|
-
function isToolBlockState(value:
|
|
265
|
+
function isToolBlockState(value: ValueRecord | ToolBlockState): value is ToolBlockState {
|
|
281
266
|
return "data" in value && "inputJson" in value;
|
|
282
267
|
}
|
|
283
|
-
|
|
284
268
|
function setFirstStreamUpdate(
|
|
285
269
|
span: SpanHandle,
|
|
286
270
|
startedAt: number,
|
|
@@ -295,23 +279,21 @@ function setFirstStreamUpdate(
|
|
|
295
279
|
responseModel: fields.responseModel,
|
|
296
280
|
});
|
|
297
281
|
}
|
|
298
|
-
|
|
299
|
-
function parseToolInput(inputJson: string): unknown {
|
|
282
|
+
function parseToolInput(inputJson: string): JsonValue {
|
|
300
283
|
if (inputJson.length === 0) return {};
|
|
301
284
|
try {
|
|
302
|
-
return JSON.parse(inputJson)
|
|
285
|
+
return JSON.parse(inputJson);
|
|
303
286
|
} catch {
|
|
304
287
|
return inputJson;
|
|
305
288
|
}
|
|
306
289
|
}
|
|
307
|
-
|
|
308
|
-
function finalizeBlock(block: UnknownRecord | ToolBlockState): UnknownRecord {
|
|
290
|
+
function finalizeBlock(block: ValueRecord | ToolBlockState) {
|
|
309
291
|
if (!isToolBlockState(block)) return block;
|
|
310
292
|
const input = parseToolInput(block.inputJson);
|
|
311
293
|
if (block.data.input === undefined) return { ...block.data, input };
|
|
312
294
|
if (block.inputJson.length === 0) return block.data;
|
|
313
|
-
const existingInput = asRecord(block.data.input);
|
|
314
|
-
const parsedInput = asRecord(input);
|
|
295
|
+
const existingInput: ValueRecord | undefined = asRecord(block.data.input);
|
|
296
|
+
const parsedInput: ValueRecord | undefined = asRecord(input);
|
|
315
297
|
return {
|
|
316
298
|
...block.data,
|
|
317
299
|
input:
|
|
@@ -320,37 +302,26 @@ function finalizeBlock(block: UnknownRecord | ToolBlockState): UnknownRecord {
|
|
|
320
302
|
: input,
|
|
321
303
|
};
|
|
322
304
|
}
|
|
323
|
-
|
|
324
|
-
function streamOutput(state: StreamState): UnknownRecord[] | undefined {
|
|
305
|
+
function streamOutput(state: StreamState): ValueRecord[] | undefined {
|
|
325
306
|
if (state.blocks.size === 0) return undefined;
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
.sort(([left], [right]) => left - right)
|
|
331
|
-
.map(([, block]) => finalizeBlock(block)),
|
|
332
|
-
},
|
|
333
|
-
];
|
|
307
|
+
const content = [...state.blocks.entries()]
|
|
308
|
+
.sort(([left], [right]) => left - right)
|
|
309
|
+
.map(([, block]) => finalizeBlock(block));
|
|
310
|
+
return [{ role: "assistant", content: content as JsonValue[] }];
|
|
334
311
|
}
|
|
335
|
-
|
|
336
312
|
function streamPartialFields(state: StreamState): SpanFields {
|
|
337
|
-
return {
|
|
338
|
-
output: streamOutput(state),
|
|
339
|
-
usage: state.usage,
|
|
340
|
-
finishReason: state.finishReason,
|
|
341
|
-
};
|
|
313
|
+
return { output: streamOutput(state), usage: state.usage, finishReason: state.finishReason };
|
|
342
314
|
}
|
|
343
|
-
|
|
344
|
-
function recordContentBlockStart(event: UnknownRecord, state: StreamState): void {
|
|
315
|
+
function recordContentBlockStart(event: ValueRecord, state: StreamState): void {
|
|
345
316
|
const index = readNumber(event.index) ?? state.blocks.size;
|
|
346
|
-
const contentBlock = asRecord(event.content_block) ?? {};
|
|
317
|
+
const contentBlock: ValueRecord = asRecord(event.content_block) ?? {};
|
|
347
318
|
const type = readString(contentBlock.type);
|
|
348
319
|
if (type === "text") {
|
|
349
320
|
state.blocks.set(index, { type, text: readString(contentBlock.text) ?? "" });
|
|
350
321
|
return;
|
|
351
322
|
}
|
|
352
323
|
if (type === "tool_use" || type === "server_tool_use") {
|
|
353
|
-
const data
|
|
324
|
+
const data = { ...contentBlock, type };
|
|
354
325
|
state.blocks.set(index, { data, inputJson: "" });
|
|
355
326
|
return;
|
|
356
327
|
}
|
|
@@ -360,12 +331,11 @@ function recordContentBlockStart(event: UnknownRecord, state: StreamState): void
|
|
|
360
331
|
}
|
|
361
332
|
state.blocks.set(index, { ...contentBlock });
|
|
362
333
|
}
|
|
363
|
-
|
|
364
334
|
function blockForDelta(
|
|
365
335
|
index: number,
|
|
366
336
|
deltaType: string | undefined,
|
|
367
337
|
state: StreamState,
|
|
368
|
-
):
|
|
338
|
+
): ValueRecord | ToolBlockState {
|
|
369
339
|
const existing = state.blocks.get(index);
|
|
370
340
|
if (existing) return existing;
|
|
371
341
|
if (deltaType === "input_json_delta") {
|
|
@@ -380,22 +350,19 @@ function blockForDelta(
|
|
|
380
350
|
state.blocks.set(index, block);
|
|
381
351
|
return block;
|
|
382
352
|
}
|
|
383
|
-
|
|
384
|
-
function appendStringField(target: UnknownRecord, key: string, value: string | undefined): void {
|
|
353
|
+
function appendStringField(target: ValueRecord, key: string, value: string | undefined): void {
|
|
385
354
|
if (value === undefined) return;
|
|
386
355
|
const existing = readString(target[key]) ?? "";
|
|
387
356
|
target[key] = `${existing}${value}`;
|
|
388
357
|
}
|
|
389
|
-
|
|
390
|
-
function appendArrayField(target: UnknownRecord, key: string, value: unknown): void {
|
|
358
|
+
function appendArrayField(target: ValueRecord, key: string, value: JsonValue | undefined): void {
|
|
391
359
|
if (value === undefined) return;
|
|
392
|
-
const existing = Array.isArray(target[key]) ?
|
|
360
|
+
const existing = Array.isArray(target[key]) ? target[key] : [];
|
|
393
361
|
target[key] = [...existing, value];
|
|
394
362
|
}
|
|
395
|
-
|
|
396
|
-
function recordContentBlockDelta(event: UnknownRecord, state: StreamState): void {
|
|
363
|
+
function recordContentBlockDelta(event: ValueRecord, state: StreamState): void {
|
|
397
364
|
const index = readNumber(event.index) ?? 0;
|
|
398
|
-
const delta = asRecord(event.delta) ?? {};
|
|
365
|
+
const delta: ValueRecord = asRecord(event.delta) ?? {};
|
|
399
366
|
const deltaType = readString(delta.type);
|
|
400
367
|
const block = blockForDelta(index, deltaType, state);
|
|
401
368
|
if (deltaType === "input_json_delta") {
|
|
@@ -405,67 +372,67 @@ function recordContentBlockDelta(event: UnknownRecord, state: StreamState): void
|
|
|
405
372
|
const data = isToolBlockState(block) ? block.data : block;
|
|
406
373
|
if (deltaType === "text_delta") appendStringField(data, "text", readString(delta.text));
|
|
407
374
|
if (deltaType === "citations_delta") appendArrayField(data, "citations", delta.citation);
|
|
408
|
-
if (deltaType === "thinking_delta")
|
|
375
|
+
if (deltaType === "thinking_delta")
|
|
409
376
|
appendStringField(data, "thinking", readString(delta.thinking));
|
|
410
|
-
}
|
|
411
377
|
if (deltaType === "signature_delta" && delta.signature !== undefined)
|
|
412
378
|
data.signature = delta.signature;
|
|
413
379
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
const
|
|
417
|
-
const type = readString(e.type);
|
|
380
|
+
function recordStreamEvent<T>(event: T, state: StreamState): SpanFields {
|
|
381
|
+
const record: ValueRecord = asRecord(event) ?? {};
|
|
382
|
+
const type = readString(record.type);
|
|
418
383
|
const fields: SpanFields = {};
|
|
419
384
|
if (type === "message_start") {
|
|
420
|
-
const message = asRecord(
|
|
385
|
+
const message: ValueRecord = asRecord(record.message) ?? {};
|
|
421
386
|
fields.responseId = readString(message.id);
|
|
422
387
|
fields.responseModel = readString(message.model);
|
|
423
388
|
state.usage = mergeUsage(state.usage, messagesUsage(message.usage));
|
|
424
389
|
}
|
|
425
|
-
if (type === "content_block_start") recordContentBlockStart(
|
|
426
|
-
if (type === "content_block_delta") recordContentBlockDelta(
|
|
390
|
+
if (type === "content_block_start") recordContentBlockStart(record, state);
|
|
391
|
+
if (type === "content_block_delta") recordContentBlockDelta(record, state);
|
|
427
392
|
if (type === "message_delta") {
|
|
428
|
-
const delta = asRecord(
|
|
393
|
+
const delta = asRecord(record.delta) ?? {};
|
|
429
394
|
state.finishReason = readString(delta.stop_reason) ?? state.finishReason;
|
|
430
|
-
state.usage = mergeUsage(state.usage, messagesUsage(
|
|
395
|
+
state.usage = mergeUsage(state.usage, messagesUsage(record.usage));
|
|
431
396
|
}
|
|
432
397
|
return fields;
|
|
433
398
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
if (
|
|
437
|
-
const
|
|
438
|
-
const signal = asRecord(
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
399
|
+
function isStreamLike<T>(value: T): value is T & StreamLike<Value> {
|
|
400
|
+
const stream = asRecord(value);
|
|
401
|
+
if (!stream) return false;
|
|
402
|
+
const controller = asRecord(stream.controller);
|
|
403
|
+
const signal = asRecord(controller?.signal);
|
|
404
|
+
if (
|
|
405
|
+
!controller ||
|
|
406
|
+
!(controller.abort instanceof Function) ||
|
|
407
|
+
!signal ||
|
|
408
|
+
typeof signal.aborted !== "boolean" ||
|
|
409
|
+
!(signal.addEventListener instanceof Function)
|
|
410
|
+
)
|
|
411
|
+
return false;
|
|
412
|
+
return stream[Symbol.asyncIterator] instanceof Function;
|
|
444
413
|
}
|
|
445
|
-
|
|
446
414
|
function createObservedMessagesStream(
|
|
447
|
-
source: StreamLike<
|
|
415
|
+
source: StreamLike<Value>,
|
|
448
416
|
span: SpanHandle,
|
|
449
417
|
startedAt: number,
|
|
450
418
|
end: (fields?: SpanFields) => void,
|
|
451
|
-
): Stream<
|
|
419
|
+
): Stream<Value> {
|
|
452
420
|
const state: StreamState = { blocks: new Map() };
|
|
453
|
-
// The iterator's finally block never runs when iteration never starts, so an
|
|
454
|
-
// abort on an unconsumed stream must end the span here. Once iteration begins,
|
|
455
|
-
// the finally block owns the span end (the SDK aborts the controller as normal
|
|
456
|
-
// cleanup when the caller stops early, which is not an error).
|
|
457
421
|
let consumed = false;
|
|
458
422
|
const signal = source.controller.signal;
|
|
459
423
|
const endAborted = () => {
|
|
460
424
|
if (consumed) return;
|
|
461
|
-
end({
|
|
425
|
+
end({
|
|
426
|
+
...streamPartialFields(state),
|
|
427
|
+
error: signal.reason instanceof Error ? signal.reason : new Error("Request aborted"),
|
|
428
|
+
});
|
|
462
429
|
};
|
|
463
430
|
if (signal.aborted) endAborted();
|
|
464
431
|
else signal.addEventListener("abort", endAborted, { once: true });
|
|
465
432
|
async function* iterator() {
|
|
466
433
|
consumed = true;
|
|
467
434
|
const sawFirst = { value: false };
|
|
468
|
-
let terminalError:
|
|
435
|
+
let terminalError: Value;
|
|
469
436
|
try {
|
|
470
437
|
for await (const event of source) {
|
|
471
438
|
const fields = recordStreamEvent(event, state);
|
|
@@ -473,91 +440,86 @@ function createObservedMessagesStream(
|
|
|
473
440
|
yield event;
|
|
474
441
|
}
|
|
475
442
|
} catch (error) {
|
|
476
|
-
terminalError = error;
|
|
443
|
+
terminalError = error as Value;
|
|
477
444
|
throw error;
|
|
478
445
|
} finally {
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
446
|
+
const fields = streamPartialFields(state);
|
|
447
|
+
if (terminalError !== undefined)
|
|
448
|
+
fields.error =
|
|
449
|
+
terminalError instanceof Error ? terminalError : new Error(String(terminalError));
|
|
450
|
+
end(fields);
|
|
483
451
|
}
|
|
484
452
|
}
|
|
485
453
|
return new Stream(() => iterator(), source.controller);
|
|
486
454
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
value: unknown,
|
|
455
|
+
function wrapStream<T>(
|
|
456
|
+
value: T,
|
|
490
457
|
span: SpanHandle,
|
|
491
458
|
startedAt: number,
|
|
492
459
|
end: (fields?: SpanFields) => void,
|
|
493
|
-
):
|
|
460
|
+
): T | Stream<Value> {
|
|
494
461
|
if (!isStreamLike(value)) return value;
|
|
495
462
|
return createObservedMessagesStream(value, span, startedAt, end);
|
|
496
463
|
}
|
|
497
|
-
|
|
498
|
-
function wrapCreate<Fn extends (...args: never[]) => unknown>(
|
|
464
|
+
function wrapCreate<Fn extends AnyFunction>(
|
|
499
465
|
original: Fn,
|
|
500
|
-
mapRequest: (body:
|
|
501
|
-
mapResponse: (response:
|
|
466
|
+
mapRequest: (body: ValueRecord) => RequestMapping,
|
|
467
|
+
mapResponse: (response: Value) => SpanFields,
|
|
502
468
|
provider: ProviderResolver,
|
|
503
469
|
): Fn {
|
|
504
470
|
if (isWrapped(original)) return original;
|
|
505
|
-
const wrapped = function (this:
|
|
506
|
-
const body = asRecord(args[0]) ?? {};
|
|
471
|
+
const wrapped: WrappedFunction = function (this: Value, ...args: Value[]): OpaqueValue {
|
|
472
|
+
const body: ValueRecord = asRecord(args[0]) ?? {};
|
|
507
473
|
const streaming = body.stream === true;
|
|
508
474
|
const request = mapRequest(body);
|
|
509
475
|
const span = startSpan(request.name, { ...request.fields, provider: provider(this) });
|
|
510
476
|
const end = endOnce(span);
|
|
511
477
|
const startedAt = Date.now();
|
|
512
|
-
|
|
513
478
|
try {
|
|
514
479
|
const result = original.apply(this, args as never[]);
|
|
515
|
-
return makeTracedPromise(result
|
|
480
|
+
return makeTracedPromise(result as InnerAPIPromise, {
|
|
481
|
+
end,
|
|
482
|
+
span,
|
|
483
|
+
startedAt,
|
|
484
|
+
streaming,
|
|
485
|
+
mapResponse,
|
|
486
|
+
}) as OpaqueValue;
|
|
516
487
|
} catch (error) {
|
|
517
|
-
end({ error });
|
|
488
|
+
end({ error: error instanceof Error ? error : new Error(String(error)) });
|
|
518
489
|
throw error;
|
|
519
490
|
}
|
|
520
491
|
};
|
|
521
|
-
return markWrapped(wrapped
|
|
492
|
+
return markWrapped(wrapped, original) as AnyFunction & Fn;
|
|
522
493
|
}
|
|
523
|
-
|
|
524
|
-
function patchInstanceMethod<T extends UnknownRecord>(
|
|
494
|
+
function patchInstanceMethod<T extends ValueRecord>(
|
|
525
495
|
target: T,
|
|
526
496
|
key: keyof T,
|
|
527
|
-
request: (body:
|
|
528
|
-
response: (value:
|
|
497
|
+
request: (body: ValueRecord) => RequestMapping,
|
|
498
|
+
response: (value: Value) => SpanFields,
|
|
529
499
|
provider: string,
|
|
530
500
|
): void {
|
|
531
501
|
const current = target[key];
|
|
532
|
-
// An inherited wrapper (from the global prototype patch) is not an own wrapper:
|
|
533
|
-
// install one over the original so the client keeps emitting spans after
|
|
534
|
-
// uninstrumentAnthropic().
|
|
535
502
|
if (isWrapped(current) && Object.hasOwn(target, key)) return;
|
|
536
503
|
const original = isWrapped(current) ? current[WRAPPED_ORIGINAL] : current;
|
|
537
|
-
if (
|
|
504
|
+
if (!(original instanceof Function)) return;
|
|
505
|
+
const callable = original as AnyFunction;
|
|
538
506
|
target[key] = wrapCreate(
|
|
539
|
-
|
|
507
|
+
callable.bind(target),
|
|
540
508
|
request,
|
|
541
509
|
response,
|
|
542
510
|
() => provider,
|
|
543
|
-
) as T[keyof T];
|
|
511
|
+
) as AnyFunction & T[keyof T];
|
|
544
512
|
}
|
|
545
|
-
|
|
546
513
|
function patchPrototype(
|
|
547
514
|
klass: { prototype: object },
|
|
548
515
|
key: string,
|
|
549
|
-
request: (body:
|
|
550
|
-
response: (value:
|
|
516
|
+
request: (body: ValueRecord) => RequestMapping,
|
|
517
|
+
response: (value: Value) => SpanFields,
|
|
551
518
|
): () => void {
|
|
552
|
-
const prototype = klass.prototype as
|
|
519
|
+
const prototype = klass.prototype as CallableRecord;
|
|
553
520
|
const original = prototype[key];
|
|
554
|
-
if (
|
|
555
|
-
prototype[key] = wrapCreate(
|
|
556
|
-
original as (...args: never[]) => unknown,
|
|
557
|
-
request,
|
|
558
|
-
response,
|
|
559
|
-
providerForResource,
|
|
560
|
-
);
|
|
521
|
+
if (!(original instanceof Function) || isWrapped(original)) return () => {};
|
|
522
|
+
prototype[key] = wrapCreate(original, request, response, providerForResource);
|
|
561
523
|
return () => {
|
|
562
524
|
prototype[key] = original;
|
|
563
525
|
};
|
|
@@ -575,13 +537,11 @@ export function wrapAnthropic<T extends AnthropicClient>(client: T): T {
|
|
|
575
537
|
wrappedClients.add(client);
|
|
576
538
|
return client;
|
|
577
539
|
}
|
|
578
|
-
|
|
579
540
|
export function instrumentAnthropic(): void {
|
|
580
541
|
if (installed) return;
|
|
581
542
|
restorePatches = [patchPrototype(Messages, "create", messagesRequest, messagesResponse)];
|
|
582
543
|
installed = true;
|
|
583
544
|
}
|
|
584
|
-
|
|
585
545
|
export function uninstrumentAnthropic(): void {
|
|
586
546
|
if (!installed) return;
|
|
587
547
|
for (const restore of restorePatches.reverse()) restore();
|