weflayr 0.26.1 → 0.28.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/dist/package.json +8 -5
- package/dist/src/auto-instrument.d.ts +4 -3
- package/dist/src/auto-instrument.js +18 -17
- package/dist/src/get-data-uri-mime-type.d.ts +17 -0
- package/dist/src/get-data-uri-mime-type.js +25 -0
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.js +5 -1
- package/dist/src/instrumentation/anthropic/get_streaming_accumulation_telemetry.d.ts +3 -1
- package/dist/src/instrumentation/anthropic/get_streaming_accumulation_telemetry.js +6 -3
- package/dist/src/instrumentation/anthropic/get_vertex_billing_telemetry.d.ts +32 -0
- package/dist/src/instrumentation/anthropic/get_vertex_billing_telemetry.js +100 -0
- package/dist/src/instrumentation/anthropic/get_web_search_telemetry.d.ts +43 -0
- package/dist/src/instrumentation/anthropic/get_web_search_telemetry.js +147 -0
- package/dist/src/instrumentation/anthropic/set_telemetry_attributes.d.ts +5 -2
- package/dist/src/instrumentation/anthropic/set_telemetry_attributes.js +17 -4
- package/dist/src/instrumentation/bedrock/get_cache_config_telemetry.d.ts +13 -1
- package/dist/src/instrumentation/bedrock/get_cache_config_telemetry.js +56 -1
- package/dist/src/instrumentation/bedrock/get_cache_telemetry.js +8 -5
- package/dist/src/instrumentation/bedrock/get_input_message_content_telemetry.js +33 -5
- package/dist/src/instrumentation/bedrock/get_model_id_telemetry.d.ts +1 -1
- package/dist/src/instrumentation/bedrock/get_model_id_telemetry.js +1 -1
- package/dist/src/instrumentation/bedrock/get_openai_chat_format_telemetry.d.ts +45 -0
- package/dist/src/instrumentation/bedrock/get_openai_chat_format_telemetry.js +251 -0
- package/dist/src/instrumentation/bedrock/get_streaming_output_message_telemetry.d.ts +6 -5
- package/dist/src/instrumentation/bedrock/get_streaming_output_message_telemetry.js +15 -12
- package/dist/src/instrumentation/bedrock/get_tool_definitions_telemetry.d.ts +2 -2
- package/dist/src/instrumentation/bedrock/get_tool_definitions_telemetry.js +12 -4
- package/dist/src/instrumentation/bedrock/set_telemetry_attributes.d.ts +4 -2
- package/dist/src/instrumentation/bedrock/set_telemetry_attributes.js +10 -3
- package/dist/src/instrumentation/cohere/get_embeddings_telemetry.js +4 -6
- package/dist/src/instrumentation/cohere/get_input_message_content_telemetry.d.ts +23 -0
- package/dist/src/instrumentation/cohere/get_input_message_content_telemetry.js +105 -0
- package/dist/src/instrumentation/cohere/get_v2_chat_telemetry.d.ts +18 -0
- package/dist/src/instrumentation/cohere/get_v2_chat_telemetry.js +186 -0
- package/dist/src/instrumentation/cohere/set_telemetry_attributes.d.ts +3 -1
- package/dist/src/instrumentation/cohere/set_telemetry_attributes.js +5 -1
- package/dist/src/instrumentation/google/get_embeddings_telemetry.d.ts +8 -4
- package/dist/src/instrumentation/google/get_embeddings_telemetry.js +68 -35
- package/dist/src/instrumentation/google/get_provider_name.d.ts +2 -1
- package/dist/src/instrumentation/google/get_provider_name.js +2 -1
- package/dist/src/instrumentation/google/get_web_search_telemetry.d.ts +38 -0
- package/dist/src/instrumentation/google/get_web_search_telemetry.js +125 -0
- package/dist/src/instrumentation/google/set_telemetry_attributes.d.ts +2 -2
- package/dist/src/instrumentation/google/set_telemetry_attributes.js +6 -2
- package/dist/src/instrumentation/mistral/full_instrumentation.d.ts +60 -0
- package/dist/src/instrumentation/mistral/full_instrumentation.js +364 -0
- package/dist/src/instrumentation/mistral/get_input_message_content_telemetry.d.ts +35 -0
- package/dist/src/instrumentation/mistral/get_input_message_content_telemetry.js +113 -0
- package/dist/src/instrumentation/mistral/get_structured_output_schema_telemetry.d.ts +13 -0
- package/dist/src/instrumentation/mistral/get_structured_output_schema_telemetry.js +24 -0
- package/dist/src/instrumentation/mistral/get_vertex_telemetry.d.ts +54 -0
- package/dist/src/instrumentation/mistral/get_vertex_telemetry.js +153 -0
- package/dist/src/instrumentation/openai/get_bedrock_mantle_telemetry.d.ts +10 -0
- package/dist/src/instrumentation/openai/get_bedrock_mantle_telemetry.js +33 -0
- package/dist/src/instrumentation/openai/get_cache_and_reasoning_telemetry.d.ts +13 -6
- package/dist/src/instrumentation/openai/get_cache_and_reasoning_telemetry.js +17 -7
- package/dist/src/instrumentation/openai/get_cache_config_telemetry.d.ts +21 -4
- package/dist/src/instrumentation/openai/get_cache_config_telemetry.js +50 -11
- package/dist/src/instrumentation/openai/get_embeddings_telemetry.js +14 -6
- package/dist/src/instrumentation/openai/get_file_part_mime_type_telemetry.d.ts +26 -0
- package/dist/src/instrumentation/openai/get_file_part_mime_type_telemetry.js +100 -0
- package/dist/src/instrumentation/openai/get_openai_compatible_provider_telemetry.d.ts +48 -0
- package/dist/src/instrumentation/openai/get_openai_compatible_provider_telemetry.js +123 -0
- package/dist/src/instrumentation/openai/get_reasoning_content_telemetry.d.ts +28 -0
- package/dist/src/instrumentation/openai/get_reasoning_content_telemetry.js +130 -0
- package/dist/src/instrumentation/openai/get_responses_telemetry.d.ts +89 -0
- package/dist/src/instrumentation/openai/get_responses_telemetry.js +587 -0
- package/dist/src/instrumentation/openai/get_service_tier_telemetry.d.ts +20 -0
- package/dist/src/instrumentation/openai/get_service_tier_telemetry.js +84 -0
- package/dist/src/instrumentation/openai/get_structured_output_schema_telemetry.d.ts +7 -5
- package/dist/src/instrumentation/openai/get_structured_output_schema_telemetry.js +15 -8
- package/dist/src/instrumentation/openai/get_vertex_openai_compatible_telemetry.d.ts +23 -0
- package/dist/src/instrumentation/openai/get_vertex_openai_compatible_telemetry.js +69 -0
- package/dist/src/instrumentation/openai/get_web_search_telemetry.d.ts +74 -0
- package/dist/src/instrumentation/openai/get_web_search_telemetry.js +259 -0
- package/dist/src/instrumentation/openai/set_telemetry_attributes.d.ts +13 -1
- package/dist/src/instrumentation/openai/set_telemetry_attributes.js +33 -2
- package/dist/src/instrumentation/shared/get_structured_output_schema_telemetry.d.ts +1 -1
- package/dist/src/instrumentation/shared/get_structured_output_schema_telemetry.js +1 -1
- package/dist/src/instrumentation/shared/is_vertex_base_url.d.ts +17 -0
- package/dist/src/instrumentation/shared/is_vertex_base_url.js +40 -0
- package/dist/src/otel/propagation.d.ts +37 -10
- package/dist/src/otel/propagation.js +62 -22
- package/dist/src/otel/span-filter.d.ts +2 -0
- package/dist/src/otel/span-filter.js +10 -1
- package/dist/src/otel/span-processor.d.ts +1 -2
- package/dist/src/otel/span-processor.js +17 -20
- package/dist/src/otel/strip-media-payloads.d.ts +26 -0
- package/dist/src/otel/strip-media-payloads.js +135 -0
- package/dist/src/propagate-metadata-to-gateway.d.ts +37 -0
- package/dist/src/propagate-metadata-to-gateway.js +213 -0
- package/package.json +8 -5
- package/dist/src/instrumentation/vercel/full_instrumentation.d.ts +0 -3
- package/dist/src/instrumentation/vercel/full_instrumentation.js +0 -54
- package/dist/src/instrumentation/vercel/get_cache_creation_telemetry.d.ts +0 -22
- package/dist/src/instrumentation/vercel/get_cache_creation_telemetry.js +0 -66
- package/dist/src/instrumentation/vercel/get_structured_output_schema_telemetry.d.ts +0 -24
- package/dist/src/instrumentation/vercel/get_structured_output_schema_telemetry.js +0 -84
- package/dist/src/otel/vercel-ai-sdk-span-filter.d.ts +0 -12
- package/dist/src/otel/vercel-ai-sdk-span-filter.js +0 -88
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Remove media payloads (base64 images, audio, documents) from the message
|
|
4
|
+
* content span attributes, right before export.
|
|
5
|
+
*
|
|
6
|
+
* An inline image, audio clip or document reaches the span in one of five shapes,
|
|
7
|
+
* depending on the instrumentor: a `blob` part carrying the base64 bytes in
|
|
8
|
+
* `content` (OpenAI chat completions, Anthropic), a `uri` part whose uri is a
|
|
9
|
+
* `data:` URI holding the same bytes (OpenAI Responses), Anthropic's own content
|
|
10
|
+
* block with the bytes under `source.data` (Bedrock), an OpenAI `file` block
|
|
11
|
+
* with the bytes under `file.file_data`, an OpenAI Responses `input_audio` block
|
|
12
|
+
* kept whole under `content` with the bytes under `content.input_audio.data`. A single
|
|
13
|
+
* image is hundreds of kilobytes, so keeping it would make every event on the
|
|
14
|
+
* queue that much bigger for nothing: the payload is never read back.
|
|
15
|
+
*
|
|
16
|
+
* Each of those becomes `{"type": "blob", "mime_type": "<mime_type>"}`, the same
|
|
17
|
+
* byte-less stub the google, cohere and bedrock instrumentation complements
|
|
18
|
+
* build themselves: the analyses reading these messages still see that the call
|
|
19
|
+
* carried content the stored text does not. A `uri` part pointing at a hosted
|
|
20
|
+
* image is a reference, not a payload, and is kept as is.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.stripMediaPayloads = stripMediaPayloads;
|
|
24
|
+
const get_data_uri_mime_type_1 = require("../get-data-uri-mime-type");
|
|
25
|
+
// The message content attributes whose JSON holds parts: the first two are
|
|
26
|
+
// `[{role, parts}]`, `gen_ai.system_instructions` is a flat parts array.
|
|
27
|
+
const MESSAGE_PARTS_ATTRIBUTES = [
|
|
28
|
+
'gen_ai.input.messages',
|
|
29
|
+
'gen_ai.output.messages',
|
|
30
|
+
'gen_ai.system_instructions',
|
|
31
|
+
];
|
|
32
|
+
function isJsonObject(value) {
|
|
33
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
34
|
+
}
|
|
35
|
+
function stripPart(part) {
|
|
36
|
+
if (!isJsonObject(part))
|
|
37
|
+
return part;
|
|
38
|
+
if (part.type === 'blob') {
|
|
39
|
+
return { type: 'blob', mime_type: String(part.mime_type ?? '') };
|
|
40
|
+
}
|
|
41
|
+
if (part.type === 'uri') {
|
|
42
|
+
const mimeType = (0, get_data_uri_mime_type_1.getDataUriMimeType)(String(part.uri ?? ''));
|
|
43
|
+
if (mimeType !== null)
|
|
44
|
+
return { type: 'blob', mime_type: mimeType };
|
|
45
|
+
}
|
|
46
|
+
// Anthropic's own content block, which the Bedrock instrumentor copies onto
|
|
47
|
+
// the span as is: the bytes sit under source.data whatever the block's type
|
|
48
|
+
// says ("image", "document", "video", "audio").
|
|
49
|
+
const source = part.source;
|
|
50
|
+
if (isJsonObject(source) && source.data != null) {
|
|
51
|
+
return { type: 'blob', mime_type: String(source.media_type ?? '') };
|
|
52
|
+
}
|
|
53
|
+
// OpenAI's file block (a PDF sent to chat completions). On Node the
|
|
54
|
+
// instrumentor already maps it to a blob part (see the openai complement that
|
|
55
|
+
// repairs its mime type), so this is the guard for the day it stops, and the
|
|
56
|
+
// mirror of the branch the Python SDK needs.
|
|
57
|
+
const fileBlock = part.file;
|
|
58
|
+
if (isJsonObject(fileBlock)) {
|
|
59
|
+
const mimeType = (0, get_data_uri_mime_type_1.getDataUriMimeType)(String(fileBlock.file_data ?? ''));
|
|
60
|
+
if (mimeType !== null)
|
|
61
|
+
return { type: 'blob', mime_type: mimeType };
|
|
62
|
+
}
|
|
63
|
+
// An input block the OpenAI Responses instrumentor does not recognize is
|
|
64
|
+
// recorded as `{"type": "<block type>", "content": <the block itself>}`, so an
|
|
65
|
+
// `input_audio` block puts its base64 clip under `content.input_audio.data`.
|
|
66
|
+
const unrecognizedBlock = part.content;
|
|
67
|
+
const inlineAudio = isJsonObject(unrecognizedBlock) ? unrecognizedBlock.input_audio : null;
|
|
68
|
+
if (isJsonObject(inlineAudio) && inlineAudio.data != null) {
|
|
69
|
+
const audioFormat = inlineAudio.format;
|
|
70
|
+
return {
|
|
71
|
+
type: 'blob',
|
|
72
|
+
mime_type: audioFormat ? `audio/${audioFormat}` : 'audio/unknown',
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// Mistral's document block. Its own tracing names the block by its type and
|
|
76
|
+
// drops the payload, so what arrives is usually `{"type": "document_url"}`
|
|
77
|
+
// already; this is what keeps the bytes off the queue if it stops dropping
|
|
78
|
+
// them.
|
|
79
|
+
const documentMimeType = (0, get_data_uri_mime_type_1.getDataUriMimeType)(String(part.document_url ?? ''));
|
|
80
|
+
if (documentMimeType !== null)
|
|
81
|
+
return { type: 'blob', mime_type: documentMimeType };
|
|
82
|
+
return part;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* `items` with `stripItem` applied to each of them, or `items` itself when
|
|
86
|
+
* every item came back unchanged - an attribute carrying no media then keeps
|
|
87
|
+
* the instrumentor's exact JSON string.
|
|
88
|
+
*/
|
|
89
|
+
function stripList(items, stripItem) {
|
|
90
|
+
const stripped = items.map(stripItem);
|
|
91
|
+
return stripped.every((item, index) => item === items[index]) ? items : stripped;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* One entry of a parts attribute: a message whose `parts` are stripped, or a
|
|
95
|
+
* bare part when the attribute is a flat parts array.
|
|
96
|
+
*/
|
|
97
|
+
function stripPartsEntry(entry) {
|
|
98
|
+
if (isJsonObject(entry) && Array.isArray(entry.parts)) {
|
|
99
|
+
const parts = stripList(entry.parts, stripPart);
|
|
100
|
+
return parts === entry.parts ? entry : { ...entry, parts };
|
|
101
|
+
}
|
|
102
|
+
return stripPart(entry);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The attribute's JSON with the media stripped from each of its entries, or
|
|
106
|
+
* null when there was nothing to strip.
|
|
107
|
+
*/
|
|
108
|
+
function getStrippedAttribute(value) {
|
|
109
|
+
if (typeof value !== 'string')
|
|
110
|
+
return null;
|
|
111
|
+
let entries;
|
|
112
|
+
try {
|
|
113
|
+
entries = JSON.parse(value);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
if (!Array.isArray(entries))
|
|
119
|
+
return null;
|
|
120
|
+
const stripped = stripList(entries, stripPartsEntry);
|
|
121
|
+
return stripped === entries ? null : JSON.stringify(stripped);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Rewrite in place every message content attribute of `attributes` that carries
|
|
125
|
+
* media bytes.
|
|
126
|
+
*/
|
|
127
|
+
function stripMediaPayloads(attributes) {
|
|
128
|
+
for (const key of MESSAGE_PARTS_ATTRIBUTES) {
|
|
129
|
+
if (key in attributes) {
|
|
130
|
+
const stripped = getStrippedAttribute(attributes[key]);
|
|
131
|
+
if (stripped !== null)
|
|
132
|
+
attributes[key] = stripped;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Send the metadata of a call to the Weflayr AI gateway in a request header.
|
|
3
|
+
*/
|
|
4
|
+
export declare const WEFLAYR_METADATA_HEADER = "x-weflayr-metadata";
|
|
5
|
+
export declare const PUBLIC_GATEWAY_HOST = "gateway.weflayr.com";
|
|
6
|
+
export declare const _state: {
|
|
7
|
+
gatewayHostAndPort: string | undefined;
|
|
8
|
+
openaiClientsPatched: boolean;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* The current scope's metadata as the JSON `WEFLAYR_METADATA_HEADER` holds.
|
|
12
|
+
*
|
|
13
|
+
* Returns `undefined` outside any `propagateMetadata` and `setCallName` scope,
|
|
14
|
+
* where there is nothing to send. Inside
|
|
15
|
+
* `propagateMetadata({ featureName: 'onboarding', customerName: 'Acme' })`,
|
|
16
|
+
* returns `'{"feature_name":"onboarding","customer_name":"Acme",
|
|
17
|
+
* "feature_run_uuid":"e3b0c442..."}'`.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildMetadataHeaderValue(): string | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Send `propagateMetadata` and `setCallName` values to the Weflayr AI gateway.
|
|
22
|
+
*
|
|
23
|
+
* Call it once at startup. Every request an `openai` client then sends to the
|
|
24
|
+
* gateway carries the metadata of the scope it is made in, in the
|
|
25
|
+
* `x-weflayr-metadata` header. Requests to any other host are left alone, and
|
|
26
|
+
* clients built before this call are covered too, since the patch is on the
|
|
27
|
+
* openai client class rather than on an instance.
|
|
28
|
+
*
|
|
29
|
+
* No event is exported for a call sent to the gateway: the gateway reports that
|
|
30
|
+
* call itself, so exporting the SDK's span too would store the call twice.
|
|
31
|
+
*
|
|
32
|
+
* @param gatewayBaseUrl The gateway's base URL when it is not the public
|
|
33
|
+
* `https://gateway.weflayr.com`, for instance a gateway running locally. Only
|
|
34
|
+
* its host and port are compared, and calling this again without it keeps the
|
|
35
|
+
* gateway already named.
|
|
36
|
+
*/
|
|
37
|
+
export declare function enablePropagateMetadataToGateway(gatewayBaseUrl?: string): void;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Send the metadata of a call to the Weflayr AI gateway in a request header.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports._state = exports.PUBLIC_GATEWAY_HOST = exports.WEFLAYR_METADATA_HEADER = void 0;
|
|
7
|
+
exports.buildMetadataHeaderValue = buildMetadataHeaderValue;
|
|
8
|
+
exports.enablePropagateMetadataToGateway = enablePropagateMetadataToGateway;
|
|
9
|
+
const api_1 = require("@opentelemetry/api");
|
|
10
|
+
const logger_1 = require("./logger");
|
|
11
|
+
const propagation_1 = require("./otel/propagation");
|
|
12
|
+
const span_filter_1 = require("./otel/span-filter");
|
|
13
|
+
// The gateway reads a call's metadata from this header, as a JSON object. A
|
|
14
|
+
// header value has to be ASCII, so a customer name written with an accent
|
|
15
|
+
// travels as JSON's own \uXXXX escape (see escapeNonAscii).
|
|
16
|
+
exports.WEFLAYR_METADATA_HEADER = 'x-weflayr-metadata';
|
|
17
|
+
exports.PUBLIC_GATEWAY_HOST = 'gateway.weflayr.com';
|
|
18
|
+
// The port a base URL points at when it names none, so "https://host/v1" and
|
|
19
|
+
// "https://host:443/v1" are read as the same gateway.
|
|
20
|
+
const DEFAULT_PORT_BY_SCHEME = { 'http:': '80', 'https:': '443' };
|
|
21
|
+
// The instrumentation scopes whose spans cover one openai call: only such a span
|
|
22
|
+
// is the call's own, and only it is stamped as sent to the gateway. Both SDKs'
|
|
23
|
+
// spellings are listed so the two stay identical.
|
|
24
|
+
const OPENAI_INSTRUMENTATION_SCOPES = [
|
|
25
|
+
'opentelemetry.instrumentation.openai',
|
|
26
|
+
'opentelemetry.instrumentation.openai_v2',
|
|
27
|
+
'@traceloop/instrumentation-openai',
|
|
28
|
+
'weflayr.instrumentation.openai',
|
|
29
|
+
];
|
|
30
|
+
// Span attribute the value is read from -> key the gateway reads it under.
|
|
31
|
+
const HEADER_KEY_BY_SPAN_ATTRIBUTE = {
|
|
32
|
+
[propagation_1.FEATURE_NAME_ATTR]: 'feature_name',
|
|
33
|
+
[propagation_1.CUSTOMER_NAME_ATTR]: 'customer_name',
|
|
34
|
+
[propagation_1.CALL_NAME_ATTR]: 'call_name',
|
|
35
|
+
[propagation_1.UUID_ATTR]: 'feature_run_uuid',
|
|
36
|
+
};
|
|
37
|
+
exports._state = { gatewayHostAndPort: undefined, openaiClientsPatched: false };
|
|
38
|
+
/**
|
|
39
|
+
* Rewrite every character above ASCII as JSON's `\uXXXX` escape.
|
|
40
|
+
*
|
|
41
|
+
* `JSON.stringify` leaves them as they are, and a header value can only hold
|
|
42
|
+
* ASCII. Each UTF-16 code unit is escaped on its own, which is what JSON asks
|
|
43
|
+
* for: an emoji's surrogate pair becomes the two escapes `\ud83d\ude00`.
|
|
44
|
+
*
|
|
45
|
+
* Example: `escapeNonAscii('{"customer_name":"Café"}')` returns
|
|
46
|
+
* `'{"customer_name":"Caf\u00e9"}'`.
|
|
47
|
+
*/
|
|
48
|
+
function escapeNonAscii(json) {
|
|
49
|
+
return json.replace(/[^\x00-\x7F]/g, (character) => {
|
|
50
|
+
const codeUnit = character.charCodeAt(0).toString(16).padStart(4, '0');
|
|
51
|
+
return `\\u${codeUnit}`;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The current scope's metadata as the JSON `WEFLAYR_METADATA_HEADER` holds.
|
|
56
|
+
*
|
|
57
|
+
* Returns `undefined` outside any `propagateMetadata` and `setCallName` scope,
|
|
58
|
+
* where there is nothing to send. Inside
|
|
59
|
+
* `propagateMetadata({ featureName: 'onboarding', customerName: 'Acme' })`,
|
|
60
|
+
* returns `'{"feature_name":"onboarding","customer_name":"Acme",
|
|
61
|
+
* "feature_run_uuid":"e3b0c442..."}'`.
|
|
62
|
+
*/
|
|
63
|
+
function buildMetadataHeaderValue() {
|
|
64
|
+
const metadata = (0, propagation_1.getMergedMetadataFromContext)();
|
|
65
|
+
if (propagation_1.BILLING_PROVIDER_ATTR in metadata) {
|
|
66
|
+
process.emitWarning('propagateMetadata: `providerNameOverride` is ignored for a call sent to the ' +
|
|
67
|
+
'Weflayr AI gateway, which reports the provider it called itself');
|
|
68
|
+
}
|
|
69
|
+
const payload = {};
|
|
70
|
+
for (const [spanAttribute, key] of Object.entries(HEADER_KEY_BY_SPAN_ATTRIBUTE)) {
|
|
71
|
+
if (spanAttribute in metadata) {
|
|
72
|
+
payload[key] = String(metadata[spanAttribute]);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const tags = {};
|
|
76
|
+
for (const [spanAttribute, value] of Object.entries(metadata)) {
|
|
77
|
+
if (spanAttribute.startsWith(propagation_1.EXTRA_TAG_PREFIX)) {
|
|
78
|
+
tags[spanAttribute.slice(propagation_1.EXTRA_TAG_PREFIX.length)] = String(value);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (Object.keys(tags).length > 0) {
|
|
82
|
+
payload.tags = tags;
|
|
83
|
+
}
|
|
84
|
+
if (Object.keys(payload).length === 0) {
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
return escapeNonAscii(JSON.stringify(payload));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The host and port a base URL points at, the port filled in from the scheme
|
|
91
|
+
* when the URL names none.
|
|
92
|
+
*
|
|
93
|
+
* Example: `readHostAndPort('https://gateway.weflayr.com/v1')` returns
|
|
94
|
+
* `'gateway.weflayr.com:443'`. Throws on a string that names no host, which
|
|
95
|
+
* includes `'localhost:8787'`: with no scheme, `URL` reads `localhost:` as the
|
|
96
|
+
* scheme and leaves the host empty.
|
|
97
|
+
*/
|
|
98
|
+
function readHostAndPort(baseUrl) {
|
|
99
|
+
const parsed = new URL(baseUrl);
|
|
100
|
+
if (!parsed.hostname) {
|
|
101
|
+
throw new Error(`no host in ${baseUrl}`);
|
|
102
|
+
}
|
|
103
|
+
const port = parsed.port || DEFAULT_PORT_BY_SCHEME[parsed.protocol] || '';
|
|
104
|
+
return `${parsed.hostname.toLowerCase()}:${port}`;
|
|
105
|
+
}
|
|
106
|
+
/** Whether a client's base URL is the gateway `enable...` was told about. */
|
|
107
|
+
function isWeflayrGatewayBaseUrl(baseUrl) {
|
|
108
|
+
const hostAndPort = readHostAndPort(baseUrl);
|
|
109
|
+
if (exports._state.gatewayHostAndPort !== undefined) {
|
|
110
|
+
return hostAndPort === exports._state.gatewayHostAndPort;
|
|
111
|
+
}
|
|
112
|
+
const hostname = hostAndPort.slice(0, hostAndPort.lastIndexOf(':'));
|
|
113
|
+
return hostname === exports.PUBLIC_GATEWAY_HOST || hostname.endsWith(`.${exports.PUBLIC_GATEWAY_HOST}`);
|
|
114
|
+
}
|
|
115
|
+
/** Stamp the span of the openai call being sent, when one is being recorded. */
|
|
116
|
+
function markCurrentSpanAsSentToGateway() {
|
|
117
|
+
const span = api_1.trace.getActiveSpan();
|
|
118
|
+
if (!span || !span.isRecording()) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const scopeName = span.instrumentationScope?.name ?? '';
|
|
122
|
+
const isOpenaiCallSpan = OPENAI_INSTRUMENTATION_SCOPES.some((scope) => scopeName === scope || scopeName.startsWith(`${scope}.`));
|
|
123
|
+
if (isOpenaiCallSpan) {
|
|
124
|
+
span.setAttribute(span_filter_1.SENT_TO_WEFLAYR_GATEWAY_ATTR, true);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Add the metadata header to `options` when the client calls the gateway.
|
|
129
|
+
*
|
|
130
|
+
* Swallows its own failures: this runs inside the app's own LLM call, which
|
|
131
|
+
* telemetry must never break. The caller's own headers are kept whichever shape
|
|
132
|
+
* they were passed in (a `Headers`, an array of pairs or a plain object), which
|
|
133
|
+
* is why they go through `Headers` rather than an object spread.
|
|
134
|
+
*
|
|
135
|
+
* The span is marked before the header is built, not after: the request is sent
|
|
136
|
+
* to the gateway either way, so a span left unmarked because building the header
|
|
137
|
+
* threw would be exported on top of the event the gateway reports, storing one
|
|
138
|
+
* call twice and doubling its cost. A call whose header never made it is stored
|
|
139
|
+
* once, with no feature, customer or call name.
|
|
140
|
+
*/
|
|
141
|
+
function addMetadataHeader(client, options) {
|
|
142
|
+
try {
|
|
143
|
+
if (!isWeflayrGatewayBaseUrl(String(client?.baseURL ?? ''))) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
markCurrentSpanAsSentToGateway();
|
|
147
|
+
const headerValue = buildMetadataHeaderValue();
|
|
148
|
+
if (headerValue !== undefined) {
|
|
149
|
+
const headers = new Headers(options.headers ?? undefined);
|
|
150
|
+
headers.set(exports.WEFLAYR_METADATA_HEADER, headerValue);
|
|
151
|
+
options.headers = headers;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
(0, logger_1.debug)('enablePropagateMetadataToGateway: could not add the metadata header');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Send `propagateMetadata` and `setCallName` values to the Weflayr AI gateway.
|
|
160
|
+
*
|
|
161
|
+
* Call it once at startup. Every request an `openai` client then sends to the
|
|
162
|
+
* gateway carries the metadata of the scope it is made in, in the
|
|
163
|
+
* `x-weflayr-metadata` header. Requests to any other host are left alone, and
|
|
164
|
+
* clients built before this call are covered too, since the patch is on the
|
|
165
|
+
* openai client class rather than on an instance.
|
|
166
|
+
*
|
|
167
|
+
* No event is exported for a call sent to the gateway: the gateway reports that
|
|
168
|
+
* call itself, so exporting the SDK's span too would store the call twice.
|
|
169
|
+
*
|
|
170
|
+
* @param gatewayBaseUrl The gateway's base URL when it is not the public
|
|
171
|
+
* `https://gateway.weflayr.com`, for instance a gateway running locally. Only
|
|
172
|
+
* its host and port are compared, and calling this again without it keeps the
|
|
173
|
+
* gateway already named.
|
|
174
|
+
*/
|
|
175
|
+
function enablePropagateMetadataToGateway(gatewayBaseUrl) {
|
|
176
|
+
if (gatewayBaseUrl !== undefined) {
|
|
177
|
+
let hostAndPort;
|
|
178
|
+
try {
|
|
179
|
+
hostAndPort = readHostAndPort(gatewayBaseUrl);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
throw new Error('weflayr.enablePropagateMetadataToGateway: `gatewayBaseUrl` needs a scheme ' +
|
|
183
|
+
`and a host, e.g. http://localhost:8787/v1, got: ${gatewayBaseUrl}`);
|
|
184
|
+
}
|
|
185
|
+
exports._state.gatewayHostAndPort = hostAndPort;
|
|
186
|
+
}
|
|
187
|
+
if (exports._state.openaiClientsPatched) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
let OpenAI;
|
|
191
|
+
try {
|
|
192
|
+
({ OpenAI } = require('openai'));
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
throw new Error('weflayr.enablePropagateMetadataToGateway: the openai client is required to ' +
|
|
196
|
+
'call the Weflayr AI gateway. Install it with: npm install openai\n' +
|
|
197
|
+
`Underlying error: ${err.message}`);
|
|
198
|
+
}
|
|
199
|
+
// `prepareOptions` is the openai client's documented hook for mutating a
|
|
200
|
+
// request's options, and it runs for every endpoint, streaming or not.
|
|
201
|
+
const originalPrepareOptions = OpenAI.prototype.prepareOptions;
|
|
202
|
+
if (typeof originalPrepareOptions !== 'function') {
|
|
203
|
+
throw new Error('weflayr.enablePropagateMetadataToGateway: the openai client has no ' +
|
|
204
|
+
'prepareOptions hook, so the installed openai version cannot send the ' +
|
|
205
|
+
'metadata header.');
|
|
206
|
+
}
|
|
207
|
+
OpenAI.prototype.prepareOptions = async function prepareOptions(options) {
|
|
208
|
+
await originalPrepareOptions.call(this, options);
|
|
209
|
+
addMetadataHeader(this, options);
|
|
210
|
+
};
|
|
211
|
+
exports._state.openaiClientsPatched = true;
|
|
212
|
+
(0, logger_1.debug)('enablePropagateMetadataToGateway: patched the openai client gatewayHostAndPort=%s', exports._state.gatewayHostAndPort ?? exports.PUBLIC_GATEWAY_HOST);
|
|
213
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "weflayr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"description": "Weflayr JS/TS SDK — instrument any LLM client",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"generate-api": "openapi-ts && node scripts/generate_facade.mjs",
|
|
14
14
|
"pretest": "npm run build",
|
|
15
15
|
"prepublishOnly": "npm run build",
|
|
16
|
-
"test": "node --test",
|
|
16
|
+
"test": "node --test \"dist/tests/**/*.test.js\"",
|
|
17
17
|
"lint": "eslint src tests",
|
|
18
18
|
"lint:fix": "eslint src tests --fix",
|
|
19
19
|
"format": "prettier --write src tests",
|
|
@@ -31,19 +31,22 @@
|
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@opentelemetry/api": "^1.9.1",
|
|
34
|
-
"@opentelemetry/exporter-trace-otlp-http": "^0.
|
|
35
|
-
"@opentelemetry/instrumentation": "^0.
|
|
34
|
+
"@opentelemetry/exporter-trace-otlp-http": "^0.222.0",
|
|
35
|
+
"@opentelemetry/instrumentation": "^0.222.0",
|
|
36
36
|
"@opentelemetry/resources": "^2.8.0",
|
|
37
37
|
"@opentelemetry/sdk-trace-base": "^2.8.0",
|
|
38
38
|
"@opentelemetry/sdk-trace-node": "^2.8.0",
|
|
39
39
|
"async-retry": "^1.3.3"
|
|
40
40
|
},
|
|
41
|
+
"overrides": {
|
|
42
|
+
"js-yaml": "^4.3.1"
|
|
43
|
+
},
|
|
41
44
|
"devDependencies": {
|
|
42
45
|
"@hey-api/openapi-ts": "^0.99.0",
|
|
43
46
|
"@opentelemetry/context-async-hooks": "^2.8.0",
|
|
44
47
|
"@types/async-retry": "^1.4.9",
|
|
45
48
|
"@types/node": "^26.1.1",
|
|
46
|
-
"eslint": "^
|
|
49
|
+
"eslint": "^10.8.0",
|
|
47
50
|
"prettier": "^3.4.2",
|
|
48
51
|
"typescript": "^6.0.3",
|
|
49
52
|
"typescript-eslint": "^8.65.0"
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.VercelAiSdkInstrumentor = void 0;
|
|
4
|
-
/**
|
|
5
|
-
* Instrumentor for the Vercel AI SDK (`ai` package, v7+).
|
|
6
|
-
*
|
|
7
|
-
* The AI SDK ships its own OTel integration in `@ai-sdk/otel`; instrumenting
|
|
8
|
-
* is registering it globally so every generateText/streamText/embed call emits
|
|
9
|
-
* GenAI semconv spans through the tracer provider weflayr's setup() installed.
|
|
10
|
-
* `usage: true` adds the `ai.usage.*` token details (reasoning tokens, uncached
|
|
11
|
-
* input) that the semconv attributes do not carry; `providerMetadata: true`
|
|
12
|
-
* opts into `ai.response.providerMetadata` on the spans that carry it.
|
|
13
|
-
*
|
|
14
|
-
* The aggregate spans the integration emits around each model call
|
|
15
|
-
* (invoke_agent, agent_step, duplicated embeddings spans) are filtered out
|
|
16
|
-
* before export by otel/vercel-ai-sdk-span-filter.js.
|
|
17
|
-
*/
|
|
18
|
-
const get_cache_creation_telemetry_1 = require("./get_cache_creation_telemetry");
|
|
19
|
-
const get_structured_output_schema_telemetry_1 = require("./get_structured_output_schema_telemetry");
|
|
20
|
-
class VercelAiSdkInstrumentor {
|
|
21
|
-
instrument() {
|
|
22
|
-
let registerTelemetry;
|
|
23
|
-
let OpenTelemetry;
|
|
24
|
-
try {
|
|
25
|
-
({ registerTelemetry } = require('ai'));
|
|
26
|
-
({ OpenTelemetry } = require('@ai-sdk/otel'));
|
|
27
|
-
}
|
|
28
|
-
catch (err) {
|
|
29
|
-
throw new Error('weflayr.autoInstrument("vercel_ai_gateway"): failed to load "ai" / "@ai-sdk/otel". ' +
|
|
30
|
-
`Install them with: npm install ai @ai-sdk/otel\nUnderlying error: ${err.message}`);
|
|
31
|
-
}
|
|
32
|
-
// registerTelemetry only exists since v7.
|
|
33
|
-
if (typeof registerTelemetry !== 'function') {
|
|
34
|
-
throw new Error('weflayr.autoInstrument("vercel_ai_gateway") requires version 7+ of the Vercel AI SDK ' +
|
|
35
|
-
'(the "ai" package), which added global telemetry registration. ' +
|
|
36
|
-
'Upgrade with: npm install ai@latest @ai-sdk/otel.\n' +
|
|
37
|
-
'On ai <= 6 telemetry can only be enabled per call: remove "vercel_ai_gateway" from ' +
|
|
38
|
-
'aiSdks (pass an empty list if no other sdks need to be instrumented). Instead, make sure ' +
|
|
39
|
-
'experimental_telemetry: { isEnabled: true } is passed on each generateText/streamText call.');
|
|
40
|
-
}
|
|
41
|
-
const dispatcher = new OpenTelemetry({ usage: true, providerMetadata: true });
|
|
42
|
-
// The integration never emits the raw provider usage, the only place the
|
|
43
|
-
// written cache tokens per TTL (Anthropic's `cache_creation`) are
|
|
44
|
-
// reported; this complement stamps it on the model-call span (see
|
|
45
|
-
// get_cache_creation_telemetry).
|
|
46
|
-
new get_cache_creation_telemetry_1.VercelCacheCreationInstrumentor().instrument(dispatcher);
|
|
47
|
-
// generateObject's schema is only stamped on the root span the weflayr
|
|
48
|
-
// filter drops; this complement re-stamps it on the model-call span (see
|
|
49
|
-
// get_structured_output_schema_telemetry).
|
|
50
|
-
new get_structured_output_schema_telemetry_1.VercelStructuredOutputSchemaInstrumentor().instrument(dispatcher);
|
|
51
|
-
registerTelemetry(dispatcher);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
exports.VercelAiSdkInstrumentor = VercelAiSdkInstrumentor;
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cache-write capture for Vercel AI SDK (ai >= 7) model-call spans.
|
|
3
|
-
*
|
|
4
|
-
* Anthropic reports the written cache tokens per TTL in its raw usage
|
|
5
|
-
* (`usage.cache_creation`, ephemeral_5m/1h buckets — billed at different
|
|
6
|
-
* rates). ai >= 7 carries that verbatim on the model-call telemetry event as
|
|
7
|
-
* `event.usage.raw` (a documented field of LanguageModelUsage), but the
|
|
8
|
-
* `@ai-sdk/otel` integration never emits it, so the span loses it.
|
|
9
|
-
*
|
|
10
|
-
* Wrap the telemetry dispatcher's `onLanguageModelCallEnd(event)` (a public
|
|
11
|
-
* callback of the telemetry interface) to read `usage.cache_creation` off the
|
|
12
|
-
* raw usage and stamp it verbatim as `weflayr.cache.creation` on the
|
|
13
|
-
* still-open model-call span (reached through the integration's call state)
|
|
14
|
-
* before the original handler ends it; the normalizer owns the mapping to TTL
|
|
15
|
-
* tiers.
|
|
16
|
-
*/
|
|
17
|
-
export declare function cacheCreationFromRawUsage(rawUsage: any): Record<string, unknown> | undefined;
|
|
18
|
-
export declare class VercelCacheCreationInstrumentor {
|
|
19
|
-
private _patched;
|
|
20
|
-
instrument(dispatcher: any): void;
|
|
21
|
-
uninstrument(): void;
|
|
22
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Cache-write capture for Vercel AI SDK (ai >= 7) model-call spans.
|
|
4
|
-
*
|
|
5
|
-
* Anthropic reports the written cache tokens per TTL in its raw usage
|
|
6
|
-
* (`usage.cache_creation`, ephemeral_5m/1h buckets — billed at different
|
|
7
|
-
* rates). ai >= 7 carries that verbatim on the model-call telemetry event as
|
|
8
|
-
* `event.usage.raw` (a documented field of LanguageModelUsage), but the
|
|
9
|
-
* `@ai-sdk/otel` integration never emits it, so the span loses it.
|
|
10
|
-
*
|
|
11
|
-
* Wrap the telemetry dispatcher's `onLanguageModelCallEnd(event)` (a public
|
|
12
|
-
* callback of the telemetry interface) to read `usage.cache_creation` off the
|
|
13
|
-
* raw usage and stamp it verbatim as `weflayr.cache.creation` on the
|
|
14
|
-
* still-open model-call span (reached through the integration's call state)
|
|
15
|
-
* before the original handler ends it; the normalizer owns the mapping to TTL
|
|
16
|
-
* tiers.
|
|
17
|
-
*/
|
|
18
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
-
exports.VercelCacheCreationInstrumentor = void 0;
|
|
20
|
-
exports.cacheCreationFromRawUsage = cacheCreationFromRawUsage;
|
|
21
|
-
const CACHE_CREATION_ATTR = 'weflayr.cache.creation';
|
|
22
|
-
// The raw `usage.cache_creation` of a provider usage, or undefined when the
|
|
23
|
-
// provider reports none (or reports only zeros — non-caching calls carry no
|
|
24
|
-
// attribute).
|
|
25
|
-
function cacheCreationFromRawUsage(rawUsage) {
|
|
26
|
-
const cacheCreation = rawUsage && rawUsage.cache_creation;
|
|
27
|
-
if (!cacheCreation)
|
|
28
|
-
return undefined;
|
|
29
|
-
return Object.values(cacheCreation).some(Boolean) ? cacheCreation : undefined;
|
|
30
|
-
}
|
|
31
|
-
class VercelCacheCreationInstrumentor {
|
|
32
|
-
// [owner, methodName, originalFunction] for uninstrument().
|
|
33
|
-
_patched = [];
|
|
34
|
-
// No-op (and never throws) if the interface is missing — the worst case is
|
|
35
|
-
// the cache-write capture staying empty, exactly the pre-complement
|
|
36
|
-
// behaviour.
|
|
37
|
-
instrument(dispatcher) {
|
|
38
|
-
if (!dispatcher || typeof dispatcher.onLanguageModelCallEnd !== 'function')
|
|
39
|
-
return;
|
|
40
|
-
const original = dispatcher.onLanguageModelCallEnd;
|
|
41
|
-
dispatcher.onLanguageModelCallEnd = function onLanguageModelCallEndWithCacheCreation(event) {
|
|
42
|
-
try {
|
|
43
|
-
const cacheCreation = cacheCreationFromRawUsage(event && event.usage && event.usage.raw);
|
|
44
|
-
if (cacheCreation) {
|
|
45
|
-
const callState = this.getCallState && this.getCallState(event.callId);
|
|
46
|
-
const span = callState && callState.inferenceSpan;
|
|
47
|
-
if (span && typeof span.setAttribute === 'function') {
|
|
48
|
-
span.setAttribute(CACHE_CREATION_ATTR, JSON.stringify(cacheCreation));
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
// Never break span finalization over an enrichment failure.
|
|
54
|
-
}
|
|
55
|
-
return original.call(this, event);
|
|
56
|
-
};
|
|
57
|
-
this._patched.push([dispatcher, 'onLanguageModelCallEnd', original]);
|
|
58
|
-
}
|
|
59
|
-
uninstrument() {
|
|
60
|
-
for (const [owner, methodName, original] of this._patched) {
|
|
61
|
-
owner[methodName] = original;
|
|
62
|
-
}
|
|
63
|
-
this._patched = [];
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
exports.VercelCacheCreationInstrumentor = VercelCacheCreationInstrumentor;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Structured-output schema capture for Vercel AI SDK (ai >= 7) model-call spans.
|
|
3
|
-
*
|
|
4
|
-
* A `generateObject` call's schema is only ever emitted on the operation's ROOT
|
|
5
|
-
* span (`onObjectOperationStart` stamps `ai.schema` there under the
|
|
6
|
-
* supplemental-attributes opt-in) — and weflayr's span filter drops that
|
|
7
|
-
* aggregate span, keeping one span per model call. So the schema never reaches
|
|
8
|
-
* the exported span, even though the gateway bills it as input.
|
|
9
|
-
*
|
|
10
|
-
* The operation and its nested model call share one `callId` in the
|
|
11
|
-
* dispatcher's call state. Wrap `onObjectOperationStart` to remember the
|
|
12
|
-
* event's schema by callId, and `onObjectStepEnd` — the object operation's own
|
|
13
|
-
* model-call hook, where the call state's `inferenceSpan` exists (object
|
|
14
|
-
* operations never fire `onLanguageModelCallEnd`; verified live) — to stamp it
|
|
15
|
-
* as `ai.schema` on that span, the attribute the worker already reads.
|
|
16
|
-
* `cleanupCallState` is wrapped so an operation that errors before its model
|
|
17
|
-
* call does not leak its entry.
|
|
18
|
-
*/
|
|
19
|
-
export declare class VercelStructuredOutputSchemaInstrumentor {
|
|
20
|
-
private _patched;
|
|
21
|
-
private _schemasByCallId;
|
|
22
|
-
instrument(dispatcher: any): void;
|
|
23
|
-
uninstrument(): void;
|
|
24
|
-
}
|