weflayr 0.16.1 → 0.20.2
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/.prettierrc.json +7 -0
- package/LICENSE-MIT +21 -0
- package/NOTICE +12 -0
- package/eslint.config.mjs +36 -0
- package/index.d.ts +125 -103
- package/package.json +19 -3
- package/src/auto-instrument.js +114 -0
- package/src/index.js +12 -34
- package/src/logger.js +8 -0
- package/src/otel/exporter.js +113 -0
- package/src/otel/openai-streaming-usage.js +60 -0
- package/src/otel/propagation.js +119 -0
- package/src/otel/span-filter.js +73 -0
- package/src/otel/span-processor.js +123 -0
- package/src/setup.js +168 -0
- package/tests/index.test.js +569 -980
- package/.env.example +0 -3
- package/src/instrument.js +0 -324
- package/src/providers/anthropic-ai-sdk.js +0 -63
- package/src/providers/bedrock-runtime.d.ts +0 -6
- package/src/providers/bedrock-runtime.js +0 -28
- package/src/vercel-ai-middleware.js +0 -124
package/.env.example
DELETED
package/src/instrument.js
DELETED
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const https = require('https');
|
|
4
|
-
const http = require('http');
|
|
5
|
-
const { randomUUID } = require('crypto');
|
|
6
|
-
const { AsyncLocalStorage } = require('async_hooks');
|
|
7
|
-
const { version: SDK_VERSION } = require('../package.json');
|
|
8
|
-
const SDK_LANGUAGE = 'javascript';
|
|
9
|
-
|
|
10
|
-
let _config = null;
|
|
11
|
-
const _asyncStore = new AsyncLocalStorage();
|
|
12
|
-
|
|
13
|
-
function configure(intakeUrl, clientId, clientSecret, settings) {
|
|
14
|
-
_asyncStore.enterWith({});
|
|
15
|
-
if (settings.ignore_fields && settings.allow_fields) {
|
|
16
|
-
console.warn('[weflayr] both ignore_fields and allow_fields are set - no events will be sent to the intake API');
|
|
17
|
-
_config = { intakeUrl, clientId, clientSecret, settings, _blocked: true };
|
|
18
|
-
return;
|
|
19
|
-
}
|
|
20
|
-
_config = { intakeUrl, clientId, clientSecret, settings, _blocked: false };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function weflayr_instrument(target) {
|
|
24
|
-
if (!_config) return target;
|
|
25
|
-
|
|
26
|
-
if (typeof target === 'function') {
|
|
27
|
-
const methodConfig = (_config.settings.methods || []).find(m => m.call === target.name);
|
|
28
|
-
if (!methodConfig) return target;
|
|
29
|
-
return _wrapFn(target, target.name, methodConfig);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (target && typeof target === 'object') {
|
|
33
|
-
return _makeProxy(target, '');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return target;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function _makeProxy(target, pathPrefix) {
|
|
40
|
-
return new Proxy(target, {
|
|
41
|
-
get(obj, prop) {
|
|
42
|
-
if (typeof prop === 'symbol') return Reflect.get(obj, prop);
|
|
43
|
-
|
|
44
|
-
const value = Reflect.get(obj, prop);
|
|
45
|
-
const fullPath = pathPrefix ? `${pathPrefix}.${prop}` : prop;
|
|
46
|
-
|
|
47
|
-
if (typeof value === 'function') {
|
|
48
|
-
const methodConfig = (_config.settings.methods || []).find(m => m.call === fullPath);
|
|
49
|
-
if (methodConfig) {
|
|
50
|
-
return _wrapFn(value.bind(obj), fullPath, methodConfig);
|
|
51
|
-
}
|
|
52
|
-
return value.bind(obj);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (value && typeof value === 'object') {
|
|
56
|
-
return _makeProxy(value, fullPath);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return value;
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function _wrapFn(fn, methodName, methodConfig) {
|
|
65
|
-
return async function (...args) {
|
|
66
|
-
const { tags, cleanArgs } = _extractTags(args);
|
|
67
|
-
const startTime = Date.now();
|
|
68
|
-
const { settings } = _config;
|
|
69
|
-
const eventId = randomUUID();
|
|
70
|
-
// For multi-arg methods (e.g. convert(voiceId, request)), use the last arg as the
|
|
71
|
-
// request object so middleware and content policy see the options, not the id.
|
|
72
|
-
const requestArgs = cleanArgs[cleanArgs.length - 1];
|
|
73
|
-
|
|
74
|
-
if (settings.event_mode !== 'light') {
|
|
75
|
-
const beforeMiddlewareData = methodConfig.middleware
|
|
76
|
-
? methodConfig.middleware(requestArgs, null) || {}
|
|
77
|
-
: {};
|
|
78
|
-
_sendEvent('before', {
|
|
79
|
-
event_id: eventId,
|
|
80
|
-
method: methodName,
|
|
81
|
-
tags,
|
|
82
|
-
args: _applyContentPolicy(requestArgs, settings),
|
|
83
|
-
...beforeMiddlewareData,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
try {
|
|
88
|
-
const result = await fn(...cleanArgs);
|
|
89
|
-
|
|
90
|
-
if (_isAsyncIterable(result)) {
|
|
91
|
-
_sendEvent('stream_start', { event_id: eventId, method: methodName, tags });
|
|
92
|
-
return _wrapStream(result, methodName, tags, requestArgs, settings, startTime, methodConfig, eventId);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (methodConfig.streamPath && result && _isAsyncIterable(result[methodConfig.streamPath])) {
|
|
96
|
-
_sendEvent('stream_start', { event_id: eventId, method: methodName, tags });
|
|
97
|
-
const wrappedStream = _wrapStream(result[methodConfig.streamPath], methodName, tags, requestArgs, settings, startTime, methodConfig, eventId, result);
|
|
98
|
-
return { ...result, [methodConfig.streamPath]: wrappedStream };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const middlewareData = methodConfig.middleware
|
|
102
|
-
? methodConfig.middleware(requestArgs, result) || {}
|
|
103
|
-
: {};
|
|
104
|
-
|
|
105
|
-
_sendEvent('after', {
|
|
106
|
-
event_id: eventId,
|
|
107
|
-
method: methodName,
|
|
108
|
-
tags,
|
|
109
|
-
args: _applyContentPolicy(requestArgs, settings),
|
|
110
|
-
response: _applyContentPolicy(_toPlain(result), settings),
|
|
111
|
-
elapsed_ms: Date.now() - startTime,
|
|
112
|
-
...middlewareData,
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
return result;
|
|
116
|
-
} catch (err) {
|
|
117
|
-
await _sendEvent('after', {
|
|
118
|
-
event_id: eventId,
|
|
119
|
-
method: methodName,
|
|
120
|
-
tags,
|
|
121
|
-
args: _applyContentPolicy(requestArgs, settings),
|
|
122
|
-
error: err.message ?? 'unknown',
|
|
123
|
-
status_code: err.status ?? err.statusCode ?? 'unknown',
|
|
124
|
-
elapsed_ms: Date.now() - startTime,
|
|
125
|
-
});
|
|
126
|
-
throw err;
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// fullResult is only set for the streamPath case — it's the full SDK result object
|
|
132
|
-
// (e.g. Mastra's StreamTextResult) so streamMiddleware factories can close over it
|
|
133
|
-
// and await properties like result.usage after the stream drains.
|
|
134
|
-
async function* _wrapStream(stream, methodName, tags, requestArgs, settings, startTime, methodConfig, eventId, fullResult) {
|
|
135
|
-
let lastChunk = null;
|
|
136
|
-
const accumulator = methodConfig.streamMiddleware ? methodConfig.streamMiddleware(fullResult) : null;
|
|
137
|
-
|
|
138
|
-
try {
|
|
139
|
-
for await (const chunk of stream) {
|
|
140
|
-
lastChunk = chunk;
|
|
141
|
-
if (accumulator && accumulator.onChunk(chunk)) {
|
|
142
|
-
_sendEvent('stream_pending', {
|
|
143
|
-
event_id: eventId,
|
|
144
|
-
method: methodName,
|
|
145
|
-
tags,
|
|
146
|
-
elapsed_ms: Date.now() - startTime,
|
|
147
|
-
...(await accumulator.finalize()),
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
yield chunk;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const rawMiddlewareData = accumulator
|
|
154
|
-
? (await accumulator.finalize()) || {}
|
|
155
|
-
: (methodConfig.middleware && lastChunk ? methodConfig.middleware(requestArgs, lastChunk) || {}
|
|
156
|
-
: {});
|
|
157
|
-
const middlewareData = _applyContentPolicy(rawMiddlewareData, settings);
|
|
158
|
-
|
|
159
|
-
_sendEvent('after', {
|
|
160
|
-
event_id: eventId,
|
|
161
|
-
method: methodName,
|
|
162
|
-
tags,
|
|
163
|
-
args: _applyContentPolicy(requestArgs, settings),
|
|
164
|
-
response: _applyContentPolicy(_toPlain(lastChunk), settings),
|
|
165
|
-
elapsed_ms: Date.now() - startTime,
|
|
166
|
-
...middlewareData,
|
|
167
|
-
});
|
|
168
|
-
} catch (err) {
|
|
169
|
-
await _sendEvent('after', {
|
|
170
|
-
event_id: eventId,
|
|
171
|
-
method: methodName,
|
|
172
|
-
tags,
|
|
173
|
-
args: _applyContentPolicy(requestArgs, settings),
|
|
174
|
-
error: err.message ?? 'unknown',
|
|
175
|
-
status_code: err.status ?? err.statusCode ?? 'unknown',
|
|
176
|
-
elapsed_ms: Date.now() - startTime,
|
|
177
|
-
});
|
|
178
|
-
throw err;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function _extractTags(args) {
|
|
183
|
-
let callTags = {};
|
|
184
|
-
|
|
185
|
-
function strip(obj) {
|
|
186
|
-
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return;
|
|
187
|
-
if ('__weflayr_tags' in obj) {
|
|
188
|
-
callTags = obj.__weflayr_tags || {};
|
|
189
|
-
delete obj.__weflayr_tags;
|
|
190
|
-
}
|
|
191
|
-
for (const key of Object.keys(obj)) {
|
|
192
|
-
strip(obj[key]);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const cleanArgs = args.map(arg => {
|
|
197
|
-
if (arg && typeof arg === 'object' && !Array.isArray(arg)) strip(arg);
|
|
198
|
-
return arg;
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
const tags = {
|
|
202
|
-
...(_config.settings.default_tags || {}),
|
|
203
|
-
..._asyncStore.getStore(),
|
|
204
|
-
...callTags,
|
|
205
|
-
};
|
|
206
|
-
|
|
207
|
-
return { tags, cleanArgs };
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function weflayr_propagate(key, value) {
|
|
211
|
-
_asyncStore.getStore()[key] = value;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
function _isAsyncIterable(value) {
|
|
216
|
-
return value != null && typeof value[Symbol.asyncIterator] === 'function';
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function _toPlain(value) {
|
|
220
|
-
if (!value) return value;
|
|
221
|
-
if (Buffer.isBuffer(value) || value instanceof Uint8Array || value instanceof ArrayBuffer) return null;
|
|
222
|
-
if (typeof value.toJSON === 'function') return value.toJSON();
|
|
223
|
-
return value;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function _applyContentPolicy(data, settings) {
|
|
227
|
-
if (!data) return data;
|
|
228
|
-
const fn = settings.ignore_fields || settings.allow_fields;
|
|
229
|
-
if (!fn) return data;
|
|
230
|
-
let clone;
|
|
231
|
-
try {
|
|
232
|
-
clone = JSON.parse(JSON.stringify(data));
|
|
233
|
-
} catch {
|
|
234
|
-
return data;
|
|
235
|
-
}
|
|
236
|
-
try {
|
|
237
|
-
return fn(clone) ?? clone;
|
|
238
|
-
} catch (err) {
|
|
239
|
-
console.warn('[weflayr] content policy function error:', err.message);
|
|
240
|
-
return clone;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const _debug = process.env.WEFLAYR_DEBUG === 'true';
|
|
245
|
-
|
|
246
|
-
function _sendEvent(eventType, data) {
|
|
247
|
-
if (_config._blocked) {
|
|
248
|
-
console.warn(`[weflayr] is blocked and can't send event, ensure your configuration is valid`);
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
const { intakeUrl, clientId, clientSecret } = _config;
|
|
252
|
-
const endpoint = `${intakeUrl.replace(/\/$/, '')}/${clientId}/`;
|
|
253
|
-
|
|
254
|
-
let body;
|
|
255
|
-
try {
|
|
256
|
-
body = JSON.stringify({
|
|
257
|
-
event_type: eventType,
|
|
258
|
-
timestamp: new Date().toISOString(),
|
|
259
|
-
sdk_version: SDK_VERSION,
|
|
260
|
-
sdk_language: SDK_LANGUAGE,
|
|
261
|
-
...data,
|
|
262
|
-
});
|
|
263
|
-
} catch (err) {
|
|
264
|
-
console.warn(`[weflayr] could not serialize event "${eventType}":`, err.message);
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (_debug) {
|
|
269
|
-
console.debug(`[weflayr] → ${eventType}`, JSON.parse(body));
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
let parsed;
|
|
273
|
-
try {
|
|
274
|
-
parsed = new URL(endpoint);
|
|
275
|
-
} catch {
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const lib = parsed.protocol === 'https:' ? https : http;
|
|
280
|
-
return new Promise((resolve) => {
|
|
281
|
-
const req = lib.request({
|
|
282
|
-
hostname: parsed.hostname,
|
|
283
|
-
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
284
|
-
path: parsed.pathname,
|
|
285
|
-
method: 'POST',
|
|
286
|
-
headers: {
|
|
287
|
-
'Content-Type': 'application/json',
|
|
288
|
-
'Content-Length': Buffer.byteLength(body),
|
|
289
|
-
'Authorization': `Bearer ${clientSecret}`,
|
|
290
|
-
},
|
|
291
|
-
}, res => {
|
|
292
|
-
res.resume();
|
|
293
|
-
if (_debug) {
|
|
294
|
-
console.debug(`[weflayr] ← ${eventType} status ${res.statusCode}`);
|
|
295
|
-
}
|
|
296
|
-
if (res.statusCode >= 400) {
|
|
297
|
-
console.warn(`[weflayr] intake returned ${res.statusCode} for event "${eventType}"`);
|
|
298
|
-
}
|
|
299
|
-
resolve();
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
req.on('error', (err) => {
|
|
303
|
-
console.warn(`[weflayr] send error for event "${eventType}":`, err.message);
|
|
304
|
-
resolve();
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
req.write(body);
|
|
308
|
-
req.end();
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Send an arbitrary event to the intake. Requires configure() to have been called first.
|
|
313
|
-
function send_event(eventType, data) {
|
|
314
|
-
if (!_config) return;
|
|
315
|
-
return _sendEvent(eventType, data);
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// Returns merged default_tags + propagated async-local tags. Used by the Vercel AI middleware.
|
|
319
|
-
function get_context_tags() {
|
|
320
|
-
if (!_config) return {};
|
|
321
|
-
return { ...(_config.settings.default_tags || {}), ..._asyncStore.getStore() };
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
module.exports = { configure, weflayr_instrument, weflayr_propagate, send_event, get_context_tags };
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
// Non-streaming: extract usage from the completed message response.
|
|
4
|
-
function middleware(request, response) {
|
|
5
|
-
if (!response) return { model: request?.model ?? null };
|
|
6
|
-
return {
|
|
7
|
-
model: response.model ?? request?.model ?? null,
|
|
8
|
-
input_tokens: response.usage?.input_tokens ?? null,
|
|
9
|
-
output_tokens: response.usage?.output_tokens ?? null,
|
|
10
|
-
cache_creation_input_tokens: response.usage?.cache_creation_input_tokens ?? null,
|
|
11
|
-
cache_read_input_tokens: response.usage?.cache_read_input_tokens ?? null,
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// Streaming: Anthropic splits usage across two events.
|
|
16
|
-
// message_start → usage.input_tokens (prompt cost)
|
|
17
|
-
// message_delta → usage.output_tokens (completion cost, cumulative final value)
|
|
18
|
-
// message_stop → no usage data
|
|
19
|
-
// onChunk returns true only when usage state changes, so _wrapStream fires
|
|
20
|
-
// stream_pending exactly twice: once after message_start, once after message_delta.
|
|
21
|
-
function createStreamAccumulator() {
|
|
22
|
-
let input_tokens = null;
|
|
23
|
-
let output_tokens = null;
|
|
24
|
-
let cache_creation_input_tokens = null;
|
|
25
|
-
let cache_read_input_tokens = null;
|
|
26
|
-
let model = null;
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
onChunk(chunk) {
|
|
30
|
-
if (chunk.type === 'message_start') {
|
|
31
|
-
const usage = chunk.message?.usage;
|
|
32
|
-
model = chunk.message?.model ?? null;
|
|
33
|
-
input_tokens = usage?.input_tokens ?? null;
|
|
34
|
-
cache_creation_input_tokens = usage?.cache_creation_input_tokens ?? null;
|
|
35
|
-
cache_read_input_tokens = usage?.cache_read_input_tokens ?? null;
|
|
36
|
-
return true;
|
|
37
|
-
}
|
|
38
|
-
if (chunk.type === 'message_delta') {
|
|
39
|
-
output_tokens = chunk.usage?.output_tokens ?? null;
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
return false;
|
|
43
|
-
},
|
|
44
|
-
finalize() {
|
|
45
|
-
return {
|
|
46
|
-
model,
|
|
47
|
-
input_tokens,
|
|
48
|
-
output_tokens,
|
|
49
|
-
cache_creation_input_tokens,
|
|
50
|
-
cache_read_input_tokens,
|
|
51
|
-
};
|
|
52
|
-
},
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Ready-to-use method config for client.messages.create.
|
|
57
|
-
const messagesCreate = {
|
|
58
|
-
call: 'messages.create',
|
|
59
|
-
middleware: middleware,
|
|
60
|
-
streamMiddleware: createStreamAccumulator,
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
module.exports = { messagesCreate, middleware, createStreamAccumulator };
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
function createStreamAccumulator() {
|
|
4
|
-
let input_tokens = null;
|
|
5
|
-
let output_tokens = null;
|
|
6
|
-
|
|
7
|
-
return {
|
|
8
|
-
onChunk(chunk) {
|
|
9
|
-
if (chunk.metadata?.usage) {
|
|
10
|
-
input_tokens = chunk.metadata.usage.inputTokens ?? null;
|
|
11
|
-
output_tokens = chunk.metadata.usage.outputTokens ?? null;
|
|
12
|
-
return true;
|
|
13
|
-
}
|
|
14
|
-
return false;
|
|
15
|
-
},
|
|
16
|
-
finalize() {
|
|
17
|
-
return { input_tokens, output_tokens };
|
|
18
|
-
},
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const converseStream = {
|
|
23
|
-
call: 'send',
|
|
24
|
-
streamPath: 'stream',
|
|
25
|
-
streamMiddleware: createStreamAccumulator,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
module.exports = { converseStream, createStreamAccumulator };
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { randomUUID } = require('crypto');
|
|
4
|
-
const { send_event, get_context_tags } = require('./instrument');
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Returns a Vercel AI SDK LanguageModelV1Middleware that emits Weflayr events.
|
|
8
|
-
* Requires weflayr_setup() to have been called first.
|
|
9
|
-
*
|
|
10
|
-
* @param {{ tags?: Record<string, string | number | boolean> }} [options]
|
|
11
|
-
* @returns {object} Vercel AI SDK middleware
|
|
12
|
-
*/
|
|
13
|
-
function weflayr_vercel_ai_middleware(options) {
|
|
14
|
-
const callTags = options?.tags ?? {};
|
|
15
|
-
const modelId = options?.model ?? 'unknown';
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
wrapGenerate: async ({ doGenerate }) => {
|
|
19
|
-
const eventId = randomUUID();
|
|
20
|
-
const t0 = Date.now();
|
|
21
|
-
const tags = { ...get_context_tags(), ...callTags };
|
|
22
|
-
|
|
23
|
-
send_event('before', {
|
|
24
|
-
event_id: eventId,
|
|
25
|
-
method: 'vercel.generate',
|
|
26
|
-
tags,
|
|
27
|
-
args: { model: modelId },
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
const result = await doGenerate();
|
|
32
|
-
send_event('after', {
|
|
33
|
-
event_id: eventId,
|
|
34
|
-
method: 'vercel.generate',
|
|
35
|
-
tags,
|
|
36
|
-
args: { model: modelId },
|
|
37
|
-
response: { usage: result.usage, finish_reason: result.finishReason },
|
|
38
|
-
elapsed_ms: Date.now() - t0,
|
|
39
|
-
});
|
|
40
|
-
return result;
|
|
41
|
-
} catch (err) {
|
|
42
|
-
send_event('after', {
|
|
43
|
-
event_id: eventId,
|
|
44
|
-
method: 'vercel.generate',
|
|
45
|
-
tags,
|
|
46
|
-
args: { model: modelId },
|
|
47
|
-
error: err.message ?? 'unknown',
|
|
48
|
-
elapsed_ms: Date.now() - t0,
|
|
49
|
-
});
|
|
50
|
-
throw err;
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
wrapStream: async ({ doStream }) => {
|
|
55
|
-
const eventId = randomUUID();
|
|
56
|
-
const t0 = Date.now();
|
|
57
|
-
const tags = { ...get_context_tags(), ...callTags };
|
|
58
|
-
|
|
59
|
-
send_event('before', {
|
|
60
|
-
event_id: eventId,
|
|
61
|
-
method: 'vercel.stream',
|
|
62
|
-
tags,
|
|
63
|
-
args: { model: modelId },
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const { stream, ...rest } = await doStream();
|
|
68
|
-
|
|
69
|
-
send_event('stream_start', {
|
|
70
|
-
event_id: eventId,
|
|
71
|
-
method: 'vercel.stream',
|
|
72
|
-
tags,
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
let finishChunk = null;
|
|
76
|
-
let errorChunk = null;
|
|
77
|
-
const patched = new TransformStream({
|
|
78
|
-
transform(chunk, controller) {
|
|
79
|
-
if (chunk.type === 'finish') finishChunk = chunk;
|
|
80
|
-
if (chunk.type === 'error') errorChunk = chunk;
|
|
81
|
-
controller.enqueue(chunk);
|
|
82
|
-
},
|
|
83
|
-
flush() {
|
|
84
|
-
if (errorChunk) {
|
|
85
|
-
send_event('after', {
|
|
86
|
-
event_id: eventId,
|
|
87
|
-
method: 'vercel.stream',
|
|
88
|
-
tags,
|
|
89
|
-
args: { model: modelId },
|
|
90
|
-
error: String(errorChunk.error),
|
|
91
|
-
elapsed_ms: Date.now() - t0,
|
|
92
|
-
});
|
|
93
|
-
} else {
|
|
94
|
-
send_event('after', {
|
|
95
|
-
event_id: eventId,
|
|
96
|
-
method: 'vercel.stream',
|
|
97
|
-
tags,
|
|
98
|
-
args: { model: modelId },
|
|
99
|
-
response: finishChunk
|
|
100
|
-
? { usage: finishChunk.usage, finish_reason: finishChunk.finishReason }
|
|
101
|
-
: null,
|
|
102
|
-
elapsed_ms: Date.now() - t0,
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
return { stream: stream.pipeThrough(patched), ...rest };
|
|
109
|
-
} catch (err) {
|
|
110
|
-
send_event('after', {
|
|
111
|
-
event_id: eventId,
|
|
112
|
-
method: 'vercel.stream',
|
|
113
|
-
tags,
|
|
114
|
-
args: { model: modelId },
|
|
115
|
-
error: err.message ?? 'unknown',
|
|
116
|
-
elapsed_ms: Date.now() - t0,
|
|
117
|
-
});
|
|
118
|
-
throw err;
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
module.exports = { weflayr_vercel_ai_middleware };
|