weflayr 0.22.2 → 0.24.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/api/generated/facade.gen.d.ts +102 -0
- package/dist/api/generated/facade.gen.js +8 -1
- package/dist/api/generated/index.d.ts +2 -2
- package/dist/api/generated/index.js +8 -1
- package/dist/api/generated/sdk.gen.d.ts +29 -1
- package/dist/api/generated/sdk.gen.js +64 -1
- package/dist/api/generated/types.gen.d.ts +356 -3
- package/index.d.ts +5 -2
- package/package.json +1 -1
- package/src/api/generated/facade.gen.ts +42 -0
- package/src/api/generated/index.ts +2 -2
- package/src/api/generated/sdk.gen.ts +64 -1
- package/src/api/generated/types.gen.ts +400 -3
- package/src/auto-instrument.js +1 -0
- package/src/instrumentation/anthropic/get_cache_and_reasoning_telemetry.js +11 -0
- package/src/instrumentation/anthropic/get_cache_config_telemetry.js +90 -0
- package/src/instrumentation/anthropic/get_tool_definitions_telemetry.js +6 -1
- package/src/instrumentation/anthropic/set_telemetry_attributes.js +2 -0
- package/src/instrumentation/bedrock/get_cache_config_telemetry.js +106 -0
- package/src/instrumentation/bedrock/get_cache_telemetry.js +11 -6
- package/src/instrumentation/bedrock/set_telemetry_attributes.js +6 -2
- package/src/instrumentation/google/get_cache_operations_telemetry.js +171 -0
- package/src/instrumentation/google/get_cache_telemetry.js +16 -2
- package/src/instrumentation/google/set_telemetry_attributes.js +8 -2
- package/src/instrumentation/openai/get_cache_config_telemetry.js +63 -0
- package/src/instrumentation/openai/set_telemetry_attributes.js +2 -0
- package/src/instrumentation/vercel/full_instrumentation.js +10 -2
- package/src/instrumentation/vercel/get_cache_creation_telemetry.js +70 -0
- package/src/otel/propagation.js +22 -9
- package/src/otel/span-filter.js +12 -12
- package/src/otel/span-processor.js +8 -0
- package/src/otel/vercel-ai-sdk-span-filter.js +1 -1
- package/tests/api/index.test.js +76 -0
- package/tests/index.test.js +67 -7
- package/tests/instrumentation/anthropic/get_cache_and_reasoning_telemetry.test.js +23 -0
- package/tests/instrumentation/anthropic/get_cache_config_telemetry.test.js +92 -0
- package/tests/instrumentation/anthropic/get_tool_definitions_telemetry.test.js +15 -0
- package/tests/instrumentation/bedrock/get_cache_config_telemetry.test.js +107 -0
- package/tests/instrumentation/bedrock/get_cache_telemetry.test.js +16 -0
- package/tests/instrumentation/google/get_cache_operations_telemetry.test.js +130 -0
- package/tests/instrumentation/openai/get_cache_config_telemetry.test.js +83 -0
- package/tests/instrumentation/vercel/get_cache_creation_telemetry.test.js +96 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Tests for the Google GenAI explicit-cache lifecycle telemetry.
|
|
4
|
+
//
|
|
5
|
+
// The @google/genai `Caches` class is stubbed with prototype
|
|
6
|
+
// create/update/delete methods mirroring the SDK shape.
|
|
7
|
+
// `instrumentCacheOperations(Caches, patched)` wraps them and pushes the
|
|
8
|
+
// restore tuples onto `patched`.
|
|
9
|
+
|
|
10
|
+
const test = require('node:test');
|
|
11
|
+
const assert = require('node:assert/strict');
|
|
12
|
+
|
|
13
|
+
const { trace, SpanStatusCode } = require('@opentelemetry/api');
|
|
14
|
+
const {
|
|
15
|
+
BasicTracerProvider,
|
|
16
|
+
InMemorySpanExporter,
|
|
17
|
+
SimpleSpanProcessor,
|
|
18
|
+
} = require('@opentelemetry/sdk-trace-base');
|
|
19
|
+
|
|
20
|
+
const {
|
|
21
|
+
instrumentCacheOperations,
|
|
22
|
+
} = require('../../../src/instrumentation/google/get_cache_operations_telemetry');
|
|
23
|
+
|
|
24
|
+
const exporter = new InMemorySpanExporter();
|
|
25
|
+
trace.setGlobalTracerProvider(
|
|
26
|
+
new BasicTracerProvider({ spanProcessors: [new SimpleSpanProcessor(exporter)] })
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
const CACHED_CONTENT = {
|
|
30
|
+
name: 'cachedContents/abc123',
|
|
31
|
+
expireTime: '2026-07-16T14:00:00Z',
|
|
32
|
+
usageMetadata: { totalTokenCount: 5300 },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
class StubCaches {
|
|
36
|
+
create(params = {}) {
|
|
37
|
+
if (params.model === 'boom') return Promise.reject(new Error('api error'));
|
|
38
|
+
return Promise.resolve(CACHED_CONTENT);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
update() {
|
|
42
|
+
return Promise.resolve(CACHED_CONTENT);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
delete() {
|
|
46
|
+
return Promise.resolve({});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function instrumented() {
|
|
51
|
+
const patched = [];
|
|
52
|
+
instrumentCacheOperations(StubCaches, patched);
|
|
53
|
+
return () => {
|
|
54
|
+
for (const [owner, methodName, original] of patched) owner[methodName] = original;
|
|
55
|
+
exporter.reset();
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
test('create span carries cache facts and content', async () => {
|
|
60
|
+
const teardown = instrumented();
|
|
61
|
+
try {
|
|
62
|
+
await new StubCaches().create({
|
|
63
|
+
model: 'models/gemini-2.5-flash',
|
|
64
|
+
config: {
|
|
65
|
+
contents: ['a very large document to cache'],
|
|
66
|
+
systemInstruction: 'Answer from the document.',
|
|
67
|
+
ttl: '3600s',
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const [span] = exporter.getFinishedSpans();
|
|
72
|
+
assert.equal(span.name, 'cache_create');
|
|
73
|
+
assert.equal(span.attributes['gen_ai.system'], 'gemini');
|
|
74
|
+
assert.equal(span.attributes['gen_ai.request.model'], 'gemini-2.5-flash');
|
|
75
|
+
assert.equal(span.attributes['weflayr.cache.operation'], 'create');
|
|
76
|
+
assert.equal(span.attributes['weflayr.cache.cached_content_id'], 'cachedContents/abc123');
|
|
77
|
+
assert.equal(span.attributes['weflayr.cache.expire_time'], '2026-07-16T14:00:00Z');
|
|
78
|
+
assert.equal(span.attributes['gen_ai.usage.input_tokens'], 5300);
|
|
79
|
+
assert.deepEqual(JSON.parse(span.attributes['gen_ai.input.messages']), [
|
|
80
|
+
{ role: 'user', parts: [{ type: 'text', content: 'a very large document to cache' }] },
|
|
81
|
+
]);
|
|
82
|
+
assert.deepEqual(JSON.parse(span.attributes['gen_ai.system_instructions']), [
|
|
83
|
+
{ type: 'text', content: 'Answer from the document.' },
|
|
84
|
+
]);
|
|
85
|
+
} finally {
|
|
86
|
+
teardown();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test('update span carries the new expiry but no token count', async () => {
|
|
91
|
+
const teardown = instrumented();
|
|
92
|
+
try {
|
|
93
|
+
await new StubCaches().update({ name: 'cachedContents/abc123', config: { ttl: '180s' } });
|
|
94
|
+
|
|
95
|
+
const [span] = exporter.getFinishedSpans();
|
|
96
|
+
assert.equal(span.name, 'cache_update');
|
|
97
|
+
assert.equal(span.attributes['weflayr.cache.operation'], 'update');
|
|
98
|
+
assert.equal(span.attributes['weflayr.cache.expire_time'], '2026-07-16T14:00:00Z');
|
|
99
|
+
assert.ok(!('gen_ai.usage.input_tokens' in span.attributes));
|
|
100
|
+
} finally {
|
|
101
|
+
teardown();
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('delete span carries operation and cache id', async () => {
|
|
106
|
+
const teardown = instrumented();
|
|
107
|
+
try {
|
|
108
|
+
await new StubCaches().delete({ name: 'cachedContents/abc123' });
|
|
109
|
+
|
|
110
|
+
const [span] = exporter.getFinishedSpans();
|
|
111
|
+
assert.equal(span.name, 'cache_delete');
|
|
112
|
+
assert.equal(span.attributes['weflayr.cache.operation'], 'delete');
|
|
113
|
+
assert.equal(span.attributes['weflayr.cache.cached_content_id'], 'cachedContents/abc123');
|
|
114
|
+
assert.ok(!('gen_ai.usage.input_tokens' in span.attributes));
|
|
115
|
+
} finally {
|
|
116
|
+
teardown();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('error span records status', async () => {
|
|
121
|
+
const teardown = instrumented();
|
|
122
|
+
try {
|
|
123
|
+
await assert.rejects(new StubCaches().create({ model: 'boom' }));
|
|
124
|
+
|
|
125
|
+
const [span] = exporter.getFinishedSpans();
|
|
126
|
+
assert.equal(span.status.code, SpanStatusCode.ERROR);
|
|
127
|
+
} finally {
|
|
128
|
+
teardown();
|
|
129
|
+
}
|
|
130
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Tests for the OpenAI request-side prompt-cache param capture.
|
|
4
|
+
//
|
|
5
|
+
// The traceloop instrumentation instance is stubbed with a `startSpan` that
|
|
6
|
+
// returns a fake span, so the wrap can be exercised without the real SDK.
|
|
7
|
+
|
|
8
|
+
const test = require('node:test');
|
|
9
|
+
const assert = require('node:assert/strict');
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
OpenAICacheConfigInstrumentor,
|
|
13
|
+
} = require('../../../src/instrumentation/openai/get_cache_config_telemetry');
|
|
14
|
+
|
|
15
|
+
function makeFakeSpan() {
|
|
16
|
+
const attributes = {};
|
|
17
|
+
return {
|
|
18
|
+
attributes,
|
|
19
|
+
setAttribute(key, value) {
|
|
20
|
+
attributes[key] = value;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function makeStubInstrumentation() {
|
|
26
|
+
return {
|
|
27
|
+
startSpan() {
|
|
28
|
+
return makeFakeSpan();
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
test('stamps prompt cache key and retention from the request params', () => {
|
|
34
|
+
const instrumentation = makeStubInstrumentation();
|
|
35
|
+
new OpenAICacheConfigInstrumentor().instrument(instrumentation);
|
|
36
|
+
|
|
37
|
+
const span = instrumentation.startSpan({
|
|
38
|
+
type: 'chat',
|
|
39
|
+
params: {
|
|
40
|
+
model: 'gpt-4o-mini',
|
|
41
|
+
prompt_cache_key: 'session-42',
|
|
42
|
+
prompt_cache_retention: '24h',
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
assert.equal(span.attributes['weflayr.cache.key'], 'session-42');
|
|
47
|
+
assert.equal(span.attributes['weflayr.cache.retention'], '24h');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('stamps prompt cache options ttl as the retention', () => {
|
|
51
|
+
const instrumentation = makeStubInstrumentation();
|
|
52
|
+
new OpenAICacheConfigInstrumentor().instrument(instrumentation);
|
|
53
|
+
|
|
54
|
+
const span = instrumentation.startSpan({
|
|
55
|
+
type: 'chat',
|
|
56
|
+
params: {
|
|
57
|
+
model: 'gpt-5.6',
|
|
58
|
+
prompt_cache_options: { ttl: '30m' },
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
assert.equal(span.attributes['weflayr.cache.retention'], '30m');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('no attributes without cache params', () => {
|
|
66
|
+
const instrumentation = makeStubInstrumentation();
|
|
67
|
+
new OpenAICacheConfigInstrumentor().instrument(instrumentation);
|
|
68
|
+
|
|
69
|
+
const span = instrumentation.startSpan({ type: 'chat', params: { model: 'gpt-4o-mini' } });
|
|
70
|
+
|
|
71
|
+
assert.ok(!('weflayr.cache.key' in span.attributes));
|
|
72
|
+
assert.ok(!('weflayr.cache.retention' in span.attributes));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('uninstrument restores the original startSpan', () => {
|
|
76
|
+
const instrumentation = makeStubInstrumentation();
|
|
77
|
+
const original = instrumentation.startSpan;
|
|
78
|
+
const instrumentor = new OpenAICacheConfigInstrumentor();
|
|
79
|
+
instrumentor.instrument(instrumentation);
|
|
80
|
+
assert.notEqual(instrumentation.startSpan, original);
|
|
81
|
+
instrumentor.uninstrument();
|
|
82
|
+
assert.equal(instrumentation.startSpan, original);
|
|
83
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Tests for the ai >= 7 cache-write capture: the telemetry dispatcher's
|
|
4
|
+
// onLanguageModelCallEnd reads the raw `usage.cache_creation` off the event's
|
|
5
|
+
// raw provider usage and stamps it verbatim on the still-open model-call span
|
|
6
|
+
// (reached through the integration's call state) before ending it.
|
|
7
|
+
|
|
8
|
+
const test = require('node:test');
|
|
9
|
+
const assert = require('node:assert/strict');
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
VercelCacheCreationInstrumentor,
|
|
13
|
+
cacheCreationFromRawUsage,
|
|
14
|
+
} = require('../../../src/instrumentation/vercel/get_cache_creation_telemetry');
|
|
15
|
+
|
|
16
|
+
const RAW_ANTHROPIC_USAGE = {
|
|
17
|
+
input_tokens: 7,
|
|
18
|
+
cache_creation_input_tokens: 6053,
|
|
19
|
+
cache_creation: { ephemeral_5m_input_tokens: 6053, ephemeral_1h_input_tokens: 0 },
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function makeFakeSpan() {
|
|
23
|
+
const attributes = {};
|
|
24
|
+
return {
|
|
25
|
+
attributes,
|
|
26
|
+
setAttribute(key, value) {
|
|
27
|
+
attributes[key] = value;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function makeInstrumentedDispatcher() {
|
|
33
|
+
const span = makeFakeSpan();
|
|
34
|
+
const endedEvents = [];
|
|
35
|
+
const dispatcher = {
|
|
36
|
+
onLanguageModelCallEnd(event) {
|
|
37
|
+
endedEvents.push(event);
|
|
38
|
+
},
|
|
39
|
+
getCallState(callId) {
|
|
40
|
+
return callId === 'call-1' ? { inferenceSpan: span } : undefined;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
const instrumentor = new VercelCacheCreationInstrumentor();
|
|
44
|
+
instrumentor.instrument(dispatcher);
|
|
45
|
+
return { dispatcher, span, endedEvents, instrumentor };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
test('stamps the raw cache_creation from the provider usage on the model-call span', () => {
|
|
49
|
+
const { dispatcher, span, endedEvents } = makeInstrumentedDispatcher();
|
|
50
|
+
|
|
51
|
+
dispatcher.onLanguageModelCallEnd({
|
|
52
|
+
callId: 'call-1',
|
|
53
|
+
usage: { inputTokens: 6060, outputTokens: 5, raw: RAW_ANTHROPIC_USAGE },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
assert.deepEqual(
|
|
57
|
+
JSON.parse(span.attributes['weflayr.cache.creation']),
|
|
58
|
+
RAW_ANTHROPIC_USAGE.cache_creation
|
|
59
|
+
);
|
|
60
|
+
// The original handler still runs (it ends the span).
|
|
61
|
+
assert.equal(endedEvents.length, 1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('stamps nothing without a cache_creation in the raw usage', () => {
|
|
65
|
+
const { dispatcher, span } = makeInstrumentedDispatcher();
|
|
66
|
+
|
|
67
|
+
dispatcher.onLanguageModelCallEnd({
|
|
68
|
+
callId: 'call-1',
|
|
69
|
+
usage: { inputTokens: 13, outputTokens: 5, raw: { input_tokens: 13 } },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
assert.ok(!('weflayr.cache.creation' in span.attributes));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('cacheCreationFromRawUsage drops an all-zero cache_creation', () => {
|
|
76
|
+
assert.deepEqual(
|
|
77
|
+
cacheCreationFromRawUsage({
|
|
78
|
+
cache_creation: { ephemeral_5m_input_tokens: 4000, ephemeral_1h_input_tokens: 1300 },
|
|
79
|
+
}),
|
|
80
|
+
{ ephemeral_5m_input_tokens: 4000, ephemeral_1h_input_tokens: 1300 }
|
|
81
|
+
);
|
|
82
|
+
assert.equal(
|
|
83
|
+
cacheCreationFromRawUsage({
|
|
84
|
+
cache_creation: { ephemeral_5m_input_tokens: 0, ephemeral_1h_input_tokens: 0 },
|
|
85
|
+
}),
|
|
86
|
+
undefined
|
|
87
|
+
);
|
|
88
|
+
assert.equal(cacheCreationFromRawUsage(undefined), undefined);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('uninstrument restores the original handler', () => {
|
|
92
|
+
const { dispatcher, instrumentor } = makeInstrumentedDispatcher();
|
|
93
|
+
assert.equal(dispatcher.onLanguageModelCallEnd.name, 'onLanguageModelCallEndWithCacheCreation');
|
|
94
|
+
instrumentor.uninstrument();
|
|
95
|
+
assert.equal(dispatcher.onLanguageModelCallEnd.name, 'onLanguageModelCallEnd');
|
|
96
|
+
});
|