lupislabs 1.0.0 → 1.0.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/README.md +221 -359
- package/dist/cost-utils.d.ts +5 -0
- package/dist/cost-utils.d.ts.map +1 -0
- package/dist/cost-utils.js +51 -0
- package/dist/cost-utils.js.map +1 -0
- package/dist/endpoints.d.ts +2 -0
- package/dist/endpoints.d.ts.map +1 -0
- package/dist/endpoints.js +2 -0
- package/dist/endpoints.js.map +1 -0
- package/dist/http-interceptor.d.ts +18 -8
- package/dist/http-interceptor.d.ts.map +1 -1
- package/dist/http-interceptor.js +164 -416
- package/dist/http-interceptor.js.map +1 -1
- package/dist/index.d.ts +33 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +96 -8
- package/dist/index.js.map +1 -1
- package/dist/interceptors/axios-interceptor.d.ts +18 -0
- package/dist/interceptors/axios-interceptor.d.ts.map +1 -0
- package/dist/interceptors/axios-interceptor.js +115 -0
- package/dist/interceptors/axios-interceptor.js.map +1 -0
- package/dist/interceptors/fetch-interceptor.d.ts +18 -0
- package/dist/interceptors/fetch-interceptor.d.ts.map +1 -0
- package/dist/interceptors/fetch-interceptor.js +228 -0
- package/dist/interceptors/fetch-interceptor.js.map +1 -0
- package/dist/interceptors/got-interceptor.d.ts +18 -0
- package/dist/interceptors/got-interceptor.d.ts.map +1 -0
- package/dist/interceptors/got-interceptor.js +103 -0
- package/dist/interceptors/got-interceptor.js.map +1 -0
- package/dist/interceptors/node-http-interceptor.d.ts +21 -0
- package/dist/interceptors/node-http-interceptor.d.ts.map +1 -0
- package/dist/interceptors/node-http-interceptor.js +301 -0
- package/dist/interceptors/node-http-interceptor.js.map +1 -0
- package/dist/providers/anthropic-handler.d.ts +3 -0
- package/dist/providers/anthropic-handler.d.ts.map +1 -0
- package/dist/providers/anthropic-handler.js +50 -0
- package/dist/providers/anthropic-handler.js.map +1 -0
- package/dist/providers/openai-handler.d.ts +3 -0
- package/dist/providers/openai-handler.d.ts.map +1 -0
- package/dist/providers/openai-handler.js +46 -0
- package/dist/providers/openai-handler.js.map +1 -0
- package/dist/providers/provider-detector.d.ts +4 -0
- package/dist/providers/provider-detector.d.ts.map +1 -0
- package/dist/providers/provider-detector.js +27 -0
- package/dist/providers/provider-detector.js.map +1 -0
- package/dist/sensitive-data-filter.d.ts +20 -0
- package/dist/sensitive-data-filter.d.ts.map +1 -0
- package/dist/sensitive-data-filter.js +280 -0
- package/dist/sensitive-data-filter.js.map +1 -0
- package/dist/trace-collector.d.ts +40 -0
- package/dist/trace-collector.d.ts.map +1 -0
- package/dist/trace-collector.js +59 -0
- package/dist/trace-collector.js.map +1 -0
- package/dist/tracer.d.ts +30 -7
- package/dist/tracer.d.ts.map +1 -1
- package/dist/tracer.js +76 -70
- package/dist/tracer.js.map +1 -1
- package/dist/types.d.ts +82 -6
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -17
package/dist/http-interceptor.js
CHANGED
|
@@ -1,12 +1,63 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { SensitiveDataFilterUtil } from './sensitive-data-filter.js';
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
|
+
import { patchFetch } from './interceptors/fetch-interceptor.js';
|
|
4
|
+
import { patchNodeHttp } from './interceptors/node-http-interceptor.js';
|
|
5
|
+
import { patchAxios } from './interceptors/axios-interceptor.js';
|
|
6
|
+
import { patchGot } from './interceptors/got-interceptor.js';
|
|
7
|
+
import { detectProvider, resolveHandler } from './providers/provider-detector.js';
|
|
3
8
|
const requireFunc = createRequire(import.meta.url);
|
|
4
9
|
export class HttpInterceptor {
|
|
5
|
-
constructor(
|
|
10
|
+
constructor(traceCollector, projectId, sensitiveDataFilter) {
|
|
6
11
|
this.isIntercepting = false;
|
|
7
|
-
this.
|
|
8
|
-
this.
|
|
12
|
+
this.currentMetadata = {};
|
|
13
|
+
this.traceCollector = traceCollector;
|
|
9
14
|
this.projectId = projectId;
|
|
15
|
+
this.sensitiveDataFilter = new SensitiveDataFilterUtil(sensitiveDataFilter || {
|
|
16
|
+
filterSensitiveData: true,
|
|
17
|
+
sensitiveDataPatterns: [],
|
|
18
|
+
redactionMode: 'mask',
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
setChatId(chatId) {
|
|
22
|
+
this.currentChatId = chatId;
|
|
23
|
+
}
|
|
24
|
+
clearChatId() {
|
|
25
|
+
this.currentChatId = undefined;
|
|
26
|
+
}
|
|
27
|
+
setMetadata(metadata) {
|
|
28
|
+
this.currentMetadata = { ...this.currentMetadata, ...metadata };
|
|
29
|
+
}
|
|
30
|
+
clearMetadata() {
|
|
31
|
+
this.currentMetadata = {};
|
|
32
|
+
}
|
|
33
|
+
createTrace(url, method, statusCode, duration, provider, requestHeaders, responseHeaders, tokenUsage, costBreakdown, model, requestBody, responseBody, error) {
|
|
34
|
+
return {
|
|
35
|
+
id: `${Date.now()}-${Math.random().toString(36).substring(7)}`,
|
|
36
|
+
projectId: this.projectId,
|
|
37
|
+
timestamp: Date.now(),
|
|
38
|
+
type: 'http_request',
|
|
39
|
+
duration,
|
|
40
|
+
url,
|
|
41
|
+
method,
|
|
42
|
+
statusCode,
|
|
43
|
+
provider,
|
|
44
|
+
requestHeaders,
|
|
45
|
+
responseHeaders,
|
|
46
|
+
tokenUsage,
|
|
47
|
+
costBreakdown,
|
|
48
|
+
model,
|
|
49
|
+
requestBody,
|
|
50
|
+
responseBody,
|
|
51
|
+
chatId: this.currentChatId,
|
|
52
|
+
metadata: Object.keys(this.currentMetadata).length > 0 ? { ...this.currentMetadata } : undefined,
|
|
53
|
+
error,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
sanitizeRequestBody(body) {
|
|
57
|
+
return this.sensitiveDataFilter.sanitizeRequestBody(body);
|
|
58
|
+
}
|
|
59
|
+
sanitizeResponseBody(body) {
|
|
60
|
+
return this.sensitiveDataFilter.sanitizeResponseBody(body);
|
|
10
61
|
}
|
|
11
62
|
startIntercepting() {
|
|
12
63
|
if (this.isIntercepting) {
|
|
@@ -14,7 +65,13 @@ export class HttpInterceptor {
|
|
|
14
65
|
}
|
|
15
66
|
this.isIntercepting = true;
|
|
16
67
|
this.patchFetch();
|
|
17
|
-
|
|
68
|
+
const enableNodeHttpEnv = typeof process !== 'undefined' ? process.env?.LUPIS_ENABLE_NODE_HTTP : undefined;
|
|
69
|
+
const shouldPatchNodeHttp = enableNodeHttpEnv === 'true' || (enableNodeHttpEnv !== 'false' && (typeof globalThis.fetch !== 'function' || enableNodeHttpEnv === undefined));
|
|
70
|
+
if (shouldPatchNodeHttp) {
|
|
71
|
+
this.patchNodeHttp();
|
|
72
|
+
}
|
|
73
|
+
this.patchAxios();
|
|
74
|
+
this.patchGot();
|
|
18
75
|
}
|
|
19
76
|
stopIntercepting() {
|
|
20
77
|
if (!this.isIntercepting)
|
|
@@ -35,6 +92,22 @@ export class HttpInterceptor {
|
|
|
35
92
|
}
|
|
36
93
|
catch (e) {
|
|
37
94
|
}
|
|
95
|
+
try {
|
|
96
|
+
const axios = requireFunc('axios');
|
|
97
|
+
if (this.originalAxiosRequest && axios.default) {
|
|
98
|
+
axios.default.request = this.originalAxiosRequest;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
}
|
|
103
|
+
try {
|
|
104
|
+
const got = requireFunc('got');
|
|
105
|
+
if (this.originalGotRequest && got.default) {
|
|
106
|
+
got.default = this.originalGotRequest;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
}
|
|
38
111
|
}
|
|
39
112
|
patchFetch() {
|
|
40
113
|
try {
|
|
@@ -42,177 +115,21 @@ export class HttpInterceptor {
|
|
|
42
115
|
return;
|
|
43
116
|
}
|
|
44
117
|
this.originalFetch = globalThis.fetch;
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
'http.method': method,
|
|
59
|
-
'http.url': url,
|
|
60
|
-
'url.full': url,
|
|
61
|
-
'http.provider': provider,
|
|
62
|
-
'lupis.project.id': self.projectId,
|
|
63
|
-
'lupis.complete': 'true',
|
|
64
|
-
},
|
|
65
|
-
});
|
|
66
|
-
if (init?.body) {
|
|
67
|
-
const requestBody = typeof init.body === 'string' ? init.body : JSON.stringify(init.body);
|
|
68
|
-
span.setAttribute('http.request.body', requestBody);
|
|
69
|
-
}
|
|
70
|
-
if (init?.headers) {
|
|
71
|
-
const headers = {};
|
|
72
|
-
if (init.headers instanceof Headers) {
|
|
73
|
-
init.headers.forEach((value, key) => {
|
|
74
|
-
headers[key] = value;
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
else if (Array.isArray(init.headers)) {
|
|
78
|
-
init.headers.forEach(([key, value]) => {
|
|
79
|
-
headers[key] = value;
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
Object.assign(headers, init.headers);
|
|
84
|
-
}
|
|
85
|
-
span.setAttribute('http.request.headers', JSON.stringify(headers));
|
|
86
|
-
}
|
|
87
|
-
try {
|
|
88
|
-
const response = await self.originalFetch(input, init);
|
|
89
|
-
const duration = Date.now() - startTime;
|
|
90
|
-
span.setAttribute('http.status_code', response.status);
|
|
91
|
-
span.setAttribute('http.response.status_code', response.status);
|
|
92
|
-
span.setAttribute('http.response.status', response.status);
|
|
93
|
-
const responseHeaders = Object.fromEntries(response.headers.entries());
|
|
94
|
-
span.setAttribute('http.response.headers', JSON.stringify(responseHeaders));
|
|
95
|
-
// Try tee() first - if it works, we have a true streaming response
|
|
96
|
-
if (response.body && typeof response.body.tee === 'function') {
|
|
97
|
-
const bodyStream = response.body;
|
|
98
|
-
const [clientStream, traceStream] = bodyStream.tee();
|
|
99
|
-
const decoder = new TextDecoder();
|
|
100
|
-
const chunks = [];
|
|
101
|
-
let providerState = undefined;
|
|
102
|
-
const trackStream = async () => {
|
|
103
|
-
const reader = traceStream.getReader();
|
|
104
|
-
try {
|
|
105
|
-
while (true) {
|
|
106
|
-
const { done, value } = await reader.read();
|
|
107
|
-
if (done) {
|
|
108
|
-
const remaining = decoder.decode();
|
|
109
|
-
if (remaining) {
|
|
110
|
-
chunks.push(remaining);
|
|
111
|
-
}
|
|
112
|
-
break;
|
|
113
|
-
}
|
|
114
|
-
if (value) {
|
|
115
|
-
const chunkText = decoder.decode(value, { stream: true });
|
|
116
|
-
if (chunkText) {
|
|
117
|
-
chunks.push(chunkText);
|
|
118
|
-
if (handler && handler.isStreamingChunk(chunkText)) {
|
|
119
|
-
const { state, result } = handler.accumulateChunk(providerState, chunkText);
|
|
120
|
-
providerState = state;
|
|
121
|
-
if (result) {
|
|
122
|
-
// no-op for now; we only send final aggregated trace to minimize noise
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
// Send only final aggregated trace
|
|
129
|
-
let bodyText = chunks.join('');
|
|
130
|
-
let normalized = bodyText;
|
|
131
|
-
try {
|
|
132
|
-
if (handler) {
|
|
133
|
-
if (providerState && Array.isArray(providerState.__rawChunks)) {
|
|
134
|
-
const data = providerState.__rawChunks;
|
|
135
|
-
const aggregatedText = providerState.__aggregatedText;
|
|
136
|
-
const toolCalls = providerState.__toolCalls;
|
|
137
|
-
const usage = providerState.__usage;
|
|
138
|
-
normalized = {
|
|
139
|
-
type: 'streaming_response',
|
|
140
|
-
provider,
|
|
141
|
-
aggregatedText,
|
|
142
|
-
data,
|
|
143
|
-
toolCalls,
|
|
144
|
-
usage,
|
|
145
|
-
contentType: response.headers.get('content-type') || undefined,
|
|
146
|
-
totalChunks: data.length,
|
|
147
|
-
totalLength: data.reduce((n, s) => n + s.length, 0),
|
|
148
|
-
isComplete: true,
|
|
149
|
-
};
|
|
150
|
-
span.setAttribute('http.response.body', JSON.stringify(normalized));
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
normalized = handler.normalizeFinal(bodyText);
|
|
154
|
-
span.setAttribute('http.response.body', typeof normalized === 'string' ? normalized : JSON.stringify(normalized));
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
span.setAttribute('http.response.body', bodyText);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
catch (e) {
|
|
162
|
-
span.setAttribute('http.response.body', bodyText);
|
|
163
|
-
}
|
|
164
|
-
span.setStatus({ code: response.ok ? api.SpanStatusCode.OK : api.SpanStatusCode.ERROR });
|
|
165
|
-
span.end();
|
|
166
|
-
}
|
|
167
|
-
catch (error) {
|
|
168
|
-
span.recordException(error);
|
|
169
|
-
span.setStatus({
|
|
170
|
-
code: api.SpanStatusCode.ERROR,
|
|
171
|
-
message: error.message
|
|
172
|
-
});
|
|
173
|
-
span.end();
|
|
174
|
-
}
|
|
175
|
-
};
|
|
176
|
-
trackStream().catch(console.warn);
|
|
177
|
-
return new Response(clientStream, {
|
|
178
|
-
headers: response.headers,
|
|
179
|
-
status: response.status,
|
|
180
|
-
statusText: response.statusText,
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
// For non-streaming responses or when tee() isn't available, handle as regular response
|
|
184
|
-
if (response.body && typeof response.body.getReader === 'function') {
|
|
185
|
-
const clone = response.clone();
|
|
186
|
-
const responseText = await self.getRawResponseText(clone);
|
|
187
|
-
let normalizedBody = responseText;
|
|
188
|
-
try {
|
|
189
|
-
if (handler) {
|
|
190
|
-
normalizedBody = handler.normalizeFinal(responseText);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
catch (e) {
|
|
194
|
-
normalizedBody = responseText;
|
|
195
|
-
}
|
|
196
|
-
span.setAttribute('http.response.body', typeof normalizedBody === 'string' ? normalizedBody : JSON.stringify(normalizedBody));
|
|
197
|
-
span.setStatus({ code: response.ok ? api.SpanStatusCode.OK : api.SpanStatusCode.ERROR });
|
|
198
|
-
span.end();
|
|
199
|
-
return response;
|
|
200
|
-
}
|
|
201
|
-
span.setAttribute('http.response.body', '');
|
|
202
|
-
span.setStatus({ code: response.ok ? api.SpanStatusCode.OK : api.SpanStatusCode.ERROR });
|
|
203
|
-
span.end();
|
|
204
|
-
return response;
|
|
205
|
-
}
|
|
206
|
-
catch (error) {
|
|
207
|
-
span.recordException(error);
|
|
208
|
-
span.setStatus({
|
|
209
|
-
code: api.SpanStatusCode.ERROR,
|
|
210
|
-
message: error.message
|
|
211
|
-
});
|
|
212
|
-
span.end();
|
|
213
|
-
throw error;
|
|
214
|
-
}
|
|
118
|
+
const context = {
|
|
119
|
+
originalFetch: this.originalFetch,
|
|
120
|
+
traceCollector: this.traceCollector,
|
|
121
|
+
projectId: this.projectId,
|
|
122
|
+
currentChatId: this.currentChatId,
|
|
123
|
+
currentMetadata: this.currentMetadata,
|
|
124
|
+
sensitiveDataFilter: this.sensitiveDataFilter,
|
|
125
|
+
createTrace: this.createTrace.bind(this),
|
|
126
|
+
detectProvider: detectProvider,
|
|
127
|
+
resolveHandler: resolveHandler,
|
|
128
|
+
getRawResponseText: this.getRawResponseText.bind(this),
|
|
129
|
+
sanitizeRequestBody: this.sanitizeRequestBody.bind(this),
|
|
130
|
+
sanitizeResponseBody: this.sanitizeResponseBody.bind(this),
|
|
215
131
|
};
|
|
132
|
+
patchFetch(context);
|
|
216
133
|
}
|
|
217
134
|
catch (e) {
|
|
218
135
|
console.warn('[Lupis SDK] Failed to patch fetch:', e);
|
|
@@ -223,169 +140,88 @@ export class HttpInterceptor {
|
|
|
223
140
|
const http = requireFunc('http');
|
|
224
141
|
const https = requireFunc('https');
|
|
225
142
|
const zlib = requireFunc('zlib');
|
|
143
|
+
console.log('[Lupis SDK] Patching Node.js http/https modules');
|
|
226
144
|
this.zlib = zlib;
|
|
227
145
|
this.originalHttpRequest = http.request;
|
|
228
146
|
this.originalHttpsRequest = https.request;
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
},
|
|
246
|
-
});
|
|
247
|
-
const startTime = Date.now();
|
|
248
|
-
let requestBody = '';
|
|
249
|
-
const req = original.call(this, options, (res) => {
|
|
250
|
-
const duration = Date.now() - startTime;
|
|
251
|
-
span.setAttribute('http.status_code', res.statusCode);
|
|
252
|
-
span.setAttribute('http.response.status_code', res.statusCode);
|
|
253
|
-
span.setAttribute('http.response.status', res.statusCode);
|
|
254
|
-
const responseHeaders = {};
|
|
255
|
-
Object.keys(res.headers || {}).forEach(key => {
|
|
256
|
-
responseHeaders[key] = res.headers[key];
|
|
257
|
-
});
|
|
258
|
-
span.setAttribute('http.response.headers', JSON.stringify(responseHeaders));
|
|
259
|
-
const responseChunks = [];
|
|
260
|
-
const originalOn = res.on.bind(res);
|
|
261
|
-
const providerHandler = self.resolveHandler(provider);
|
|
262
|
-
const contentType = res.headers['content-type'] || '';
|
|
263
|
-
const isStreaming = contentType.includes('text/event-stream');
|
|
264
|
-
let streamState = undefined;
|
|
265
|
-
res.on = function (event, handler) {
|
|
266
|
-
if (event === 'data') {
|
|
267
|
-
return originalOn(event, (chunk) => {
|
|
268
|
-
responseChunks.push(Buffer.from(chunk));
|
|
269
|
-
if (isStreaming && providerHandler) {
|
|
270
|
-
const chunkText = chunk.toString('utf8');
|
|
271
|
-
if (providerHandler.isStreamingChunk(chunkText)) {
|
|
272
|
-
const { state } = providerHandler.accumulateChunk(streamState, chunkText);
|
|
273
|
-
streamState = state;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
return handler(chunk);
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
if (event === 'end') {
|
|
280
|
-
return originalOn(event, () => {
|
|
281
|
-
if (responseChunks.length > 0) {
|
|
282
|
-
try {
|
|
283
|
-
const buffer = Buffer.concat(responseChunks);
|
|
284
|
-
const contentEncoding = res.headers['content-encoding'];
|
|
285
|
-
const decompressed = self.decompressIfNeeded(buffer, contentEncoding);
|
|
286
|
-
const limitedText = decompressed.length > 1000000
|
|
287
|
-
? decompressed.substring(0, 1000000) + '...[truncated]'
|
|
288
|
-
: decompressed;
|
|
289
|
-
let normalizedBody = limitedText;
|
|
290
|
-
if (providerHandler) {
|
|
291
|
-
if (isStreaming && streamState && Array.isArray(streamState.__rawChunks)) {
|
|
292
|
-
normalizedBody = {
|
|
293
|
-
type: 'streaming_response',
|
|
294
|
-
provider,
|
|
295
|
-
aggregatedText: streamState.__aggregatedText,
|
|
296
|
-
data: streamState.__rawChunks,
|
|
297
|
-
toolCalls: streamState.__toolCalls,
|
|
298
|
-
usage: streamState.__usage,
|
|
299
|
-
contentType,
|
|
300
|
-
totalChunks: streamState.__rawChunks.length,
|
|
301
|
-
totalLength: streamState.__rawChunks.reduce((n, s) => n + s.length, 0),
|
|
302
|
-
isComplete: true,
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
else {
|
|
306
|
-
normalizedBody = providerHandler.normalizeFinal(limitedText);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
span.setAttribute('http.response.body', typeof normalizedBody === 'string' ? normalizedBody : JSON.stringify(normalizedBody));
|
|
310
|
-
}
|
|
311
|
-
catch (e) {
|
|
312
|
-
span.setAttribute('http.response.body', `[Unable to process response: ${e instanceof Error ? e.message : String(e)}]`);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
span.setStatus({
|
|
316
|
-
code: res.statusCode >= 200 && res.statusCode < 400 ?
|
|
317
|
-
api.SpanStatusCode.OK : api.SpanStatusCode.ERROR
|
|
318
|
-
});
|
|
319
|
-
span.end();
|
|
320
|
-
return handler();
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
return originalOn(event, handler);
|
|
324
|
-
};
|
|
325
|
-
if (callback) {
|
|
326
|
-
callback(res);
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
const originalWrite = req.write.bind(req);
|
|
330
|
-
req.write = function (chunk, encoding, callback) {
|
|
331
|
-
if (chunk) {
|
|
332
|
-
requestBody += chunk.toString();
|
|
333
|
-
}
|
|
334
|
-
return originalWrite(chunk, encoding, callback);
|
|
335
|
-
};
|
|
336
|
-
const originalEnd = req.end.bind(req);
|
|
337
|
-
req.end = function (chunk, encoding, callback) {
|
|
338
|
-
if (chunk) {
|
|
339
|
-
requestBody += chunk.toString();
|
|
340
|
-
}
|
|
341
|
-
if (requestBody) {
|
|
342
|
-
span.setAttribute('http.request.body', requestBody);
|
|
343
|
-
}
|
|
344
|
-
if (options.headers) {
|
|
345
|
-
span.setAttribute('http.request.headers', JSON.stringify(options.headers));
|
|
346
|
-
}
|
|
347
|
-
return originalEnd(chunk, encoding, callback);
|
|
348
|
-
};
|
|
349
|
-
req.on('error', (error) => {
|
|
350
|
-
span.recordException(error);
|
|
351
|
-
span.setStatus({
|
|
352
|
-
code: api.SpanStatusCode.ERROR,
|
|
353
|
-
message: error.message
|
|
354
|
-
});
|
|
355
|
-
span.end();
|
|
356
|
-
});
|
|
357
|
-
return req;
|
|
358
|
-
};
|
|
147
|
+
const context = {
|
|
148
|
+
originalHttpRequest: this.originalHttpRequest,
|
|
149
|
+
originalHttpsRequest: this.originalHttpsRequest,
|
|
150
|
+
zlib: this.zlib,
|
|
151
|
+
traceCollector: this.traceCollector,
|
|
152
|
+
projectId: this.projectId,
|
|
153
|
+
currentChatId: this.currentChatId,
|
|
154
|
+
currentMetadata: this.currentMetadata,
|
|
155
|
+
sensitiveDataFilter: this.sensitiveDataFilter,
|
|
156
|
+
createTrace: this.createTrace.bind(this),
|
|
157
|
+
detectProvider: detectProvider,
|
|
158
|
+
resolveHandler: resolveHandler,
|
|
159
|
+
decompressIfNeeded: this.decompressIfNeeded.bind(this),
|
|
160
|
+
requireFunc: requireFunc,
|
|
161
|
+
sanitizeRequestBody: this.sanitizeRequestBody.bind(this),
|
|
162
|
+
sanitizeResponseBody: this.sanitizeResponseBody.bind(this),
|
|
359
163
|
};
|
|
360
|
-
|
|
361
|
-
|
|
164
|
+
console.log('[Lupis SDK] Patching Node.js http/https modules with context:', context);
|
|
165
|
+
patchNodeHttp(context);
|
|
362
166
|
}
|
|
363
167
|
catch (e) {
|
|
364
168
|
console.warn('[Lupis SDK] Failed to patch Node.js http/https:', e);
|
|
365
169
|
}
|
|
366
170
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
171
|
+
patchAxios() {
|
|
172
|
+
try {
|
|
173
|
+
const axios = requireFunc('axios');
|
|
174
|
+
if (!axios || (!axios.default && !axios.request)) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const axiosInstance = axios.default || axios;
|
|
178
|
+
if (!axiosInstance.request) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
this.originalAxiosRequest = axiosInstance.request;
|
|
182
|
+
const context = {
|
|
183
|
+
originalAxiosRequest: this.originalAxiosRequest,
|
|
184
|
+
traceCollector: this.traceCollector,
|
|
185
|
+
projectId: this.projectId,
|
|
186
|
+
currentChatId: this.currentChatId,
|
|
187
|
+
currentMetadata: this.currentMetadata,
|
|
188
|
+
sensitiveDataFilter: this.sensitiveDataFilter,
|
|
189
|
+
createTrace: this.createTrace.bind(this),
|
|
190
|
+
detectProvider: detectProvider,
|
|
191
|
+
resolveHandler: resolveHandler,
|
|
192
|
+
requireFunc: requireFunc,
|
|
193
|
+
sanitizeRequestBody: this.sanitizeRequestBody.bind(this),
|
|
194
|
+
sanitizeResponseBody: this.sanitizeResponseBody.bind(this),
|
|
195
|
+
};
|
|
196
|
+
patchAxios(context);
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
}
|
|
379
200
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
return
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
201
|
+
patchGot() {
|
|
202
|
+
try {
|
|
203
|
+
const got = requireFunc('got');
|
|
204
|
+
if (!got.default) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
this.originalGotRequest = got.default;
|
|
208
|
+
const context = {
|
|
209
|
+
originalGotRequest: this.originalGotRequest,
|
|
210
|
+
traceCollector: this.traceCollector,
|
|
211
|
+
projectId: this.projectId,
|
|
212
|
+
currentChatId: this.currentChatId,
|
|
213
|
+
currentMetadata: this.currentMetadata,
|
|
214
|
+
sensitiveDataFilter: this.sensitiveDataFilter,
|
|
215
|
+
createTrace: this.createTrace.bind(this),
|
|
216
|
+
detectProvider: detectProvider,
|
|
217
|
+
resolveHandler: resolveHandler,
|
|
218
|
+
requireFunc: requireFunc,
|
|
219
|
+
sanitizeRequestBody: this.sanitizeRequestBody.bind(this),
|
|
220
|
+
sanitizeResponseBody: this.sanitizeResponseBody.bind(this),
|
|
221
|
+
};
|
|
222
|
+
patchGot(context);
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
389
225
|
}
|
|
390
226
|
}
|
|
391
227
|
async getRawResponseText(response) {
|
|
@@ -426,92 +262,4 @@ export class HttpInterceptor {
|
|
|
426
262
|
}
|
|
427
263
|
}
|
|
428
264
|
}
|
|
429
|
-
const OpenAIHandler = {
|
|
430
|
-
provider: 'openai',
|
|
431
|
-
detect: (url) => url.includes('api.openai.com'),
|
|
432
|
-
isStreamingChunk: (textChunk) => textChunk.includes('\ndata: '),
|
|
433
|
-
accumulateChunk: (state, textChunk) => {
|
|
434
|
-
const next = state || { __rawChunks: [], __aggregatedText: '', __toolCalls: [], __usage: null };
|
|
435
|
-
next.__rawChunks.push(textChunk);
|
|
436
|
-
const lines = textChunk.split('\n');
|
|
437
|
-
for (const line of lines) {
|
|
438
|
-
if (!line.startsWith('data: '))
|
|
439
|
-
continue;
|
|
440
|
-
const payload = line.substring(6).trim();
|
|
441
|
-
if (payload === '[DONE]')
|
|
442
|
-
continue;
|
|
443
|
-
try {
|
|
444
|
-
const json = JSON.parse(payload);
|
|
445
|
-
if (Array.isArray(json.choices)) {
|
|
446
|
-
for (const choice of json.choices) {
|
|
447
|
-
if (choice.delta?.content)
|
|
448
|
-
next.__aggregatedText += choice.delta.content;
|
|
449
|
-
if (Array.isArray(choice.delta?.tool_calls))
|
|
450
|
-
next.__toolCalls.push(...choice.delta.tool_calls);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
if (json?.usage) {
|
|
454
|
-
next.__usage = json.usage;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
catch { }
|
|
458
|
-
}
|
|
459
|
-
return { state: next };
|
|
460
|
-
},
|
|
461
|
-
normalizeFinal: (rawBodyText) => {
|
|
462
|
-
try {
|
|
463
|
-
const parsed = JSON.parse(rawBodyText);
|
|
464
|
-
const aggregatedText = parsed.choices?.[0]?.message?.content || '';
|
|
465
|
-
return { ...parsed, aggregatedText };
|
|
466
|
-
}
|
|
467
|
-
catch {
|
|
468
|
-
return rawBodyText;
|
|
469
|
-
}
|
|
470
|
-
},
|
|
471
|
-
};
|
|
472
|
-
const AnthropicHandler = {
|
|
473
|
-
provider: 'claude',
|
|
474
|
-
detect: (url) => url.includes('api.anthropic.com'),
|
|
475
|
-
isStreamingChunk: (textChunk) => textChunk.includes('\nevent: ') || textChunk.includes('\ndata: '),
|
|
476
|
-
accumulateChunk: (state, textChunk) => {
|
|
477
|
-
const next = state || { __rawChunks: [], __aggregatedText: '', __toolCalls: [], __usage: null };
|
|
478
|
-
next.__rawChunks.push(textChunk);
|
|
479
|
-
const lines = textChunk.split('\n');
|
|
480
|
-
for (const line of lines) {
|
|
481
|
-
if (!line.startsWith('data: '))
|
|
482
|
-
continue;
|
|
483
|
-
try {
|
|
484
|
-
const json = JSON.parse(line.substring(6));
|
|
485
|
-
if (json?.delta?.text)
|
|
486
|
-
next.__aggregatedText += json.delta.text;
|
|
487
|
-
if (json?.type === 'tool_use') {
|
|
488
|
-
next.__toolCalls.push({
|
|
489
|
-
id: json.id,
|
|
490
|
-
name: json.name,
|
|
491
|
-
type: 'function',
|
|
492
|
-
function: { name: json.name, arguments: JSON.stringify(json.input || {}) },
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
if (json?.usage) {
|
|
496
|
-
next.__usage = json.usage;
|
|
497
|
-
}
|
|
498
|
-
if (json?.type === 'message_delta' && json?.usage) {
|
|
499
|
-
next.__usage = json.usage;
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
catch { }
|
|
503
|
-
}
|
|
504
|
-
return { state: next };
|
|
505
|
-
},
|
|
506
|
-
normalizeFinal: (rawBodyText) => {
|
|
507
|
-
try {
|
|
508
|
-
const parsed = JSON.parse(rawBodyText);
|
|
509
|
-
const aggregatedText = parsed.content?.[0]?.text || '';
|
|
510
|
-
return { ...parsed, aggregatedText };
|
|
511
|
-
}
|
|
512
|
-
catch {
|
|
513
|
-
return rawBodyText;
|
|
514
|
-
}
|
|
515
|
-
},
|
|
516
|
-
};
|
|
517
265
|
//# sourceMappingURL=http-interceptor.js.map
|