@dropout-ai/runtime 0.3.3 â 0.3.5
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/package.json +1 -1
- package/src/index.js +228 -299
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @dropout-ai/runtime
|
|
3
3
|
* Universal AI Interaction Capture for Node.js & Next.js
|
|
4
|
-
*
|
|
5
|
-
* Behavior: Fire-and-forget raw JSON to Supabase.
|
|
4
|
+
* Patches: fetch, http.request, https.request
|
|
6
5
|
* Capability: Captures Genkit, OpenAI, LangChain, Axios, and standard fetch.
|
|
7
6
|
*/
|
|
8
7
|
|
|
@@ -19,355 +18,285 @@ const KNOWN_AI_DOMAINS = [
|
|
|
19
18
|
'aiplatform.googleapis.com', 'api.groq.com', 'api.mistral.ai', 'api.cohere.ai'
|
|
20
19
|
];
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
let isPatched = false;
|
|
34
|
-
let turnIndex = 0;
|
|
35
|
-
let lastTurnConfirmed = true;
|
|
36
|
-
let lastPromptHash = null;
|
|
37
|
-
let lastResponseHash = null;
|
|
38
|
-
let instance = null;
|
|
39
|
-
|
|
40
|
-
// --- UTILS ---
|
|
41
|
-
|
|
42
|
-
function log(msg, ...args) {
|
|
43
|
-
if (activeConfig.debug) console.log(`[Dropout] ${msg}`, ...args);
|
|
44
|
-
}
|
|
21
|
+
class Dropout {
|
|
22
|
+
constructor(config = {}) {
|
|
23
|
+
// đ¨ BROWSER GUARD đ¨
|
|
24
|
+
// If this runs in the browser (Client Component), do nothing.
|
|
25
|
+
// This prevents the "listener" error and protects your API keys.
|
|
26
|
+
if (typeof window !== 'undefined') {
|
|
27
|
+
console.warn("[Dropout] â ī¸ Initialization Skipped: This SDK is for Node.js/Server environments only.");
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
45
30
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
}
|
|
31
|
+
// 1. Validation
|
|
32
|
+
if (!config.apiKey || !config.projectId) {
|
|
33
|
+
console.warn("[Dropout] â ī¸ Initialization Skipped: Missing apiKey or projectId.");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
53
36
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
37
|
+
// 2. Config Setup
|
|
38
|
+
this.config = config;
|
|
39
|
+
this.projectId = config.projectId;
|
|
40
|
+
this.apiKey = config.apiKey;
|
|
41
|
+
this.debug = config.debug || false;
|
|
42
|
+
this.privacy = config.privacy || 'full';
|
|
43
|
+
this.captureEndpoint = SUPABASE_FUNCTION_URL;
|
|
44
|
+
|
|
45
|
+
// 3. Singleton Guard
|
|
46
|
+
if (global.__dropout_initialized__) {
|
|
47
|
+
if (this.debug) console.log("[Dropout] âšī¸ Already initialized. Skipping patch.");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
global.__dropout_initialized__ = true;
|
|
60
51
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (url) {
|
|
66
|
-
const u = url.toLowerCase();
|
|
67
|
-
if (u.includes('openai')) provider = 'openai';
|
|
68
|
-
else if (u.includes('anthropic')) provider = 'anthropic';
|
|
69
|
-
else if (u.includes('google') || u.includes('generative') || u.includes('aiplatform')) provider = 'google';
|
|
70
|
-
else if (u.includes('groq')) provider = 'groq';
|
|
71
|
-
else if (u.includes('mistral')) provider = 'mistral';
|
|
72
|
-
else if (u.includes('cohere')) provider = 'cohere';
|
|
73
|
-
else if (u.includes('localhost') || u.includes('127.0.0.1')) provider = 'local';
|
|
74
|
-
}
|
|
52
|
+
// 4. Initialize Identity
|
|
53
|
+
if (!global.__dropout_session_id__) {
|
|
54
|
+
global.__dropout_session_id__ = this.generateSessionId();
|
|
55
|
+
}
|
|
75
56
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const parsed = typeof body === 'string' ? JSON.parse(body) : body;
|
|
79
|
-
if (parsed.model) model = parsed.model;
|
|
80
|
-
} catch (e) { }
|
|
81
|
-
}
|
|
82
|
-
return { provider, model };
|
|
83
|
-
}
|
|
57
|
+
// 5. Start the Wiretap
|
|
58
|
+
if (this.debug) console.log(`[Dropout] đĸ Initialized for Project: ${this.projectId}`);
|
|
84
59
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
60
|
+
// Bind methods
|
|
61
|
+
this.log = this.log.bind(this);
|
|
62
|
+
this.isAiRequest = this.isAiRequest.bind(this);
|
|
63
|
+
this.patchNetwork();
|
|
64
|
+
}
|
|
89
65
|
|
|
90
|
-
//
|
|
91
|
-
|
|
66
|
+
// --- UTILS ---
|
|
67
|
+
log(msg, ...args) {
|
|
68
|
+
if (this.debug) console.log(`[Dropout] ${msg}`, ...args);
|
|
69
|
+
}
|
|
92
70
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
71
|
+
generateSessionId() {
|
|
72
|
+
try {
|
|
73
|
+
return crypto.randomUUID();
|
|
74
|
+
} catch (e) {
|
|
75
|
+
return 'sess_' + Math.random().toString(36).substring(2, 12) + Date.now().toString(36);
|
|
76
|
+
}
|
|
97
77
|
}
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
78
|
|
|
101
|
-
|
|
79
|
+
hash(text) {
|
|
80
|
+
if (!text) return null;
|
|
81
|
+
try {
|
|
82
|
+
return crypto.createHash('sha256').update(text.toLowerCase().trim()).digest('hex');
|
|
83
|
+
} catch (e) { return 'hash_err'; }
|
|
84
|
+
}
|
|
102
85
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
86
|
+
normalize(url, body) {
|
|
87
|
+
let provider = 'custom';
|
|
88
|
+
let model = 'unknown';
|
|
89
|
+
|
|
90
|
+
if (url) {
|
|
91
|
+
const u = url.toLowerCase();
|
|
92
|
+
if (u.includes('openai')) provider = 'openai';
|
|
93
|
+
else if (u.includes('anthropic')) provider = 'anthropic';
|
|
94
|
+
else if (u.includes('google') || u.includes('gemini') || u.includes('aiplatform')) provider = 'google';
|
|
95
|
+
else if (u.includes('groq')) provider = 'groq';
|
|
96
|
+
else if (u.includes('mistral')) provider = 'mistral';
|
|
97
|
+
else if (u.includes('cohere')) provider = 'cohere';
|
|
98
|
+
else if (u.includes('localhost') || u.includes('127.0.0.1')) provider = 'local';
|
|
99
|
+
}
|
|
106
100
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
101
|
+
if (body) {
|
|
102
|
+
try {
|
|
103
|
+
const parsed = typeof body === 'string' ? JSON.parse(body) : body;
|
|
104
|
+
if (parsed.model) model = parsed.model;
|
|
105
|
+
} catch (e) { }
|
|
106
|
+
}
|
|
107
|
+
return { provider, model };
|
|
108
|
+
}
|
|
111
109
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
};
|
|
110
|
+
isAiRequest(url, bodyString) {
|
|
111
|
+
if (!url) return false;
|
|
112
|
+
if (url.includes(this.captureEndpoint)) return false;
|
|
116
113
|
|
|
117
|
-
|
|
114
|
+
if (KNOWN_AI_DOMAINS.some(d => url.includes(d))) return true;
|
|
118
115
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
headers: {
|
|
123
|
-
'Content-Type': 'application/json',
|
|
124
|
-
'x-dropout-key': activeConfig.apiKey
|
|
116
|
+
if (bodyString) {
|
|
117
|
+
return (bodyString.includes('"model"') || bodyString.includes('"messages"')) &&
|
|
118
|
+
(bodyString.includes('"user"') || bodyString.includes('"prompt"'));
|
|
125
119
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
req.on('error', (e) => log("â Upload Failed", e.message));
|
|
129
|
-
req.write(JSON.stringify(finalPayload));
|
|
130
|
-
req.end();
|
|
131
|
-
}
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
132
122
|
|
|
133
|
-
// ---
|
|
123
|
+
// --- EMITTER ---
|
|
124
|
+
emit(payload) {
|
|
125
|
+
if (!this.apiKey || !this.projectId) return;
|
|
134
126
|
|
|
135
|
-
|
|
136
|
-
|
|
127
|
+
const finalPayload = {
|
|
128
|
+
...payload,
|
|
129
|
+
project_id: this.projectId,
|
|
130
|
+
//content_blob: payload.content,
|
|
131
|
+
content: payload.content,
|
|
132
|
+
received_at: new Date().toISOString()
|
|
133
|
+
};
|
|
137
134
|
|
|
138
|
-
|
|
139
|
-
if (GLOBAL_OBJ.fetch) {
|
|
140
|
-
GLOBAL_OBJ.__dropout_original_fetch__ = GLOBAL_OBJ.fetch;
|
|
141
|
-
GLOBAL_OBJ.fetch = async function (input, init) {
|
|
142
|
-
const url = typeof input === 'string' ? input : (input && input.url);
|
|
135
|
+
this.log(`đ Sending Capture (${payload.turn_role})`);
|
|
143
136
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
137
|
+
const req = https.request(this.captureEndpoint, {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: {
|
|
140
|
+
'Content-Type': 'application/json',
|
|
141
|
+
'x-dropout-key': this.apiKey
|
|
148
142
|
}
|
|
143
|
+
});
|
|
149
144
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
log(`⥠[FETCH] Intercepting: ${url}`);
|
|
145
|
+
req.on('error', (e) => this.log("â Upload Failed", e.message));
|
|
146
|
+
req.write(JSON.stringify(finalPayload));
|
|
147
|
+
req.end();
|
|
148
|
+
}
|
|
156
149
|
|
|
157
|
-
|
|
150
|
+
// --- CORE PATCHING ---
|
|
151
|
+
patchNetwork() {
|
|
152
|
+
const _this = this;
|
|
158
153
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
} else {
|
|
165
|
-
activeTurn = turnIndex > 0 ? turnIndex - 1 : 0;
|
|
166
|
-
}
|
|
154
|
+
// --- A. PATCH FETCH ---
|
|
155
|
+
if (global.fetch) {
|
|
156
|
+
const originalFetch = global.fetch;
|
|
157
|
+
global.fetch = async function (input, init) {
|
|
158
|
+
const url = typeof input === 'string' ? input : input?.url;
|
|
167
159
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
// Emit Request (User)
|
|
172
|
-
emit({
|
|
173
|
-
session_id: GLOBAL_OBJ.__dropout_session_id__,
|
|
174
|
-
turn_index: activeTurn,
|
|
175
|
-
direction: 'user_to_ai',
|
|
176
|
-
turn_role: 'user',
|
|
177
|
-
provider,
|
|
178
|
-
model,
|
|
179
|
-
content: activeConfig.privacyMode === 'full' ? bodyStr : null,
|
|
180
|
-
content_hash: pHash,
|
|
181
|
-
metadata_flags: {
|
|
182
|
-
retry_like: pHash === lastPromptHash ? 1 : 0,
|
|
183
|
-
method: 'FETCH',
|
|
184
|
-
url: url
|
|
160
|
+
let bodyStr = "";
|
|
161
|
+
if (init && init.body) {
|
|
162
|
+
try { bodyStr = typeof init.body === 'string' ? init.body : JSON.stringify(init.body); } catch (e) { }
|
|
185
163
|
}
|
|
186
|
-
});
|
|
187
|
-
lastPromptHash = pHash;
|
|
188
164
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
response = await GLOBAL_OBJ.__dropout_original_fetch__(input, init);
|
|
193
|
-
} catch (err) { throw err; }
|
|
165
|
+
if (!_this.isAiRequest(url, bodyStr)) {
|
|
166
|
+
return originalFetch.apply(this, arguments);
|
|
167
|
+
}
|
|
194
168
|
|
|
195
|
-
|
|
169
|
+
_this.log(`⥠[FETCH] Intercepting: ${url}`);
|
|
170
|
+
const start = Date.now();
|
|
196
171
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
emit({
|
|
204
|
-
session_id: GLOBAL_OBJ.__dropout_session_id__,
|
|
205
|
-
turn_index: activeTurn,
|
|
206
|
-
direction: 'ai_to_user',
|
|
207
|
-
turn_role: 'assistant',
|
|
208
|
-
latency_ms: latency,
|
|
172
|
+
const { provider, model } = _this.normalize(url, bodyStr);
|
|
173
|
+
_this.emit({
|
|
174
|
+
session_id: global.__dropout_session_id__,
|
|
175
|
+
turn_index: 0,
|
|
176
|
+
turn_role: 'user',
|
|
209
177
|
provider,
|
|
210
178
|
model,
|
|
211
|
-
content:
|
|
212
|
-
|
|
213
|
-
metadata_flags: {
|
|
214
|
-
non_adaptive_response: oHash === lastResponseHash ? 1 : 0,
|
|
215
|
-
turn_boundary_confirmed: 1,
|
|
216
|
-
status: response.status
|
|
217
|
-
}
|
|
179
|
+
content: _this.privacy === 'full' ? bodyStr : null,
|
|
180
|
+
metadata_flags: { method: 'FETCH', url: url }
|
|
218
181
|
});
|
|
219
|
-
lastResponseHash = oHash;
|
|
220
|
-
} catch (e) { log("â ī¸ Failed to read response body"); }
|
|
221
|
-
|
|
222
|
-
lastTurnConfirmed = true;
|
|
223
|
-
return response;
|
|
224
|
-
};
|
|
225
|
-
log("â
Patch Applied: global.fetch");
|
|
226
|
-
}
|
|
227
182
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
// Resolve URL
|
|
233
|
-
let url;
|
|
234
|
-
let options;
|
|
235
|
-
if (typeof args[0] === 'string') {
|
|
236
|
-
url = args[0];
|
|
237
|
-
options = args[1] || {};
|
|
238
|
-
} else {
|
|
239
|
-
options = args[0] || {};
|
|
240
|
-
const protocol = options.protocol || 'https:';
|
|
241
|
-
const host = options.hostname || options.host || 'localhost';
|
|
242
|
-
const path = options.path || '/';
|
|
243
|
-
url = `${protocol}//${host}${path}`;
|
|
244
|
-
}
|
|
183
|
+
let response;
|
|
184
|
+
try {
|
|
185
|
+
response = await originalFetch.apply(this, arguments);
|
|
186
|
+
} catch (err) { throw err; }
|
|
245
187
|
|
|
246
|
-
|
|
247
|
-
if (!isAiRequest(url, null)) {
|
|
248
|
-
return originalRequest.apply(this, args);
|
|
249
|
-
}
|
|
188
|
+
const latency = Date.now() - start;
|
|
250
189
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
190
|
+
try {
|
|
191
|
+
const cloned = response.clone();
|
|
192
|
+
const oText = await cloned.text();
|
|
193
|
+
_this.emit({
|
|
194
|
+
session_id: global.__dropout_session_id__,
|
|
195
|
+
turn_index: 0,
|
|
196
|
+
turn_role: 'assistant',
|
|
197
|
+
latency_ms: latency,
|
|
198
|
+
provider,
|
|
199
|
+
model,
|
|
200
|
+
content: _this.privacy === 'full' ? oText : null,
|
|
201
|
+
metadata_flags: { status: response.status }
|
|
202
|
+
});
|
|
203
|
+
} catch (e) { _this.log("â ī¸ Failed to read response body"); }
|
|
260
204
|
|
|
261
|
-
|
|
262
|
-
if (writeArgs[0]) reqChunks.push(Buffer.from(writeArgs[0]));
|
|
263
|
-
return originalWrite.apply(this, writeArgs);
|
|
205
|
+
return response;
|
|
264
206
|
};
|
|
207
|
+
this.log("â
Patch Applied: global.fetch");
|
|
208
|
+
}
|
|
265
209
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
let activeTurn;
|
|
275
|
-
if (lastTurnConfirmed) {
|
|
276
|
-
activeTurn = turnIndex++;
|
|
277
|
-
lastTurnConfirmed = false;
|
|
210
|
+
// --- B. PATCH NODE HTTP/HTTPS ---
|
|
211
|
+
const patchNodeRequest = (module, moduleName) => {
|
|
212
|
+
const originalRequest = module.request;
|
|
213
|
+
module.request = function (...args) {
|
|
214
|
+
let url;
|
|
215
|
+
if (typeof args[0] === 'string') {
|
|
216
|
+
url = args[0];
|
|
278
217
|
} else {
|
|
279
|
-
|
|
218
|
+
const opts = args[0] || {};
|
|
219
|
+
const protocol = opts.protocol || (moduleName === 'https' ? 'https:' : 'http:');
|
|
220
|
+
const host = opts.hostname || opts.host || 'localhost';
|
|
221
|
+
const path = opts.path || '/';
|
|
222
|
+
url = `${protocol}//${host}${path}`;
|
|
280
223
|
}
|
|
281
224
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
clientRequest._dropout_model = model;
|
|
225
|
+
if (!_this.isAiRequest(url, null)) {
|
|
226
|
+
return originalRequest.apply(this, args);
|
|
227
|
+
}
|
|
286
228
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
turn_index: activeTurn,
|
|
290
|
-
direction: 'user_to_ai',
|
|
291
|
-
turn_role: 'user',
|
|
292
|
-
provider,
|
|
293
|
-
model,
|
|
294
|
-
content: activeConfig.privacyMode === 'full' ? reqBody : null,
|
|
295
|
-
content_hash: pHash,
|
|
296
|
-
metadata_flags: { method: moduleName.toUpperCase(), url: url }
|
|
297
|
-
});
|
|
298
|
-
lastPromptHash = pHash;
|
|
229
|
+
_this.log(`⥠[${moduleName.toUpperCase()}] Intercepting: ${url}`);
|
|
230
|
+
const start = Date.now();
|
|
299
231
|
|
|
300
|
-
|
|
301
|
-
|
|
232
|
+
const clientRequest = originalRequest.apply(this, args);
|
|
233
|
+
const reqChunks = [];
|
|
234
|
+
const originalWrite = clientRequest.write;
|
|
235
|
+
const originalEnd = clientRequest.end;
|
|
302
236
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
237
|
+
clientRequest.write = function (...writeArgs) {
|
|
238
|
+
const chunk = writeArgs[0];
|
|
239
|
+
if (chunk && (typeof chunk === 'string' || Buffer.isBuffer(chunk))) {
|
|
240
|
+
reqChunks.push(Buffer.from(chunk));
|
|
241
|
+
}
|
|
242
|
+
return originalWrite.apply(this, writeArgs);
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
clientRequest.end = function (...endArgs) {
|
|
246
|
+
const chunk = endArgs[0];
|
|
247
|
+
if (chunk && (typeof chunk === 'string' || Buffer.isBuffer(chunk))) {
|
|
248
|
+
reqChunks.push(Buffer.from(chunk));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const reqBody = Buffer.concat(reqChunks).toString('utf8');
|
|
252
|
+
const { provider, model } = _this.normalize(url, reqBody);
|
|
253
|
+
clientRequest._dropout_meta = { provider, model };
|
|
254
|
+
|
|
255
|
+
_this.emit({
|
|
256
|
+
session_id: global.__dropout_session_id__,
|
|
257
|
+
turn_index: 0,
|
|
258
|
+
turn_role: 'user',
|
|
259
|
+
provider,
|
|
260
|
+
model,
|
|
261
|
+
content: _this.privacy === 'full' ? reqBody : null,
|
|
262
|
+
metadata_flags: { method: moduleName.toUpperCase(), url: url }
|
|
323
263
|
});
|
|
324
264
|
|
|
325
|
-
|
|
326
|
-
|
|
265
|
+
return originalEnd.apply(this, endArgs);
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
clientRequest.on('response', (res) => {
|
|
269
|
+
const resChunks = [];
|
|
270
|
+
res.on('data', (chunk) => resChunks.push(chunk));
|
|
271
|
+
res.on('end', () => {
|
|
272
|
+
const resBody = Buffer.concat(resChunks).toString('utf8');
|
|
273
|
+
const latency = Date.now() - start;
|
|
274
|
+
const meta = clientRequest._dropout_meta || {};
|
|
275
|
+
|
|
276
|
+
_this.emit({
|
|
277
|
+
session_id: global.__dropout_session_id__,
|
|
278
|
+
turn_index: 0,
|
|
279
|
+
turn_role: 'assistant',
|
|
280
|
+
latency_ms: latency,
|
|
281
|
+
provider: meta.provider,
|
|
282
|
+
model: meta.model,
|
|
283
|
+
content: _this.privacy === 'full' ? resBody : null,
|
|
284
|
+
metadata_flags: { status: res.statusCode }
|
|
285
|
+
});
|
|
286
|
+
});
|
|
327
287
|
});
|
|
328
|
-
});
|
|
329
288
|
|
|
330
|
-
|
|
289
|
+
return clientRequest;
|
|
290
|
+
};
|
|
331
291
|
};
|
|
332
|
-
};
|
|
333
|
-
|
|
334
|
-
patchNodeRequest(https, 'https');
|
|
335
|
-
patchNodeRequest(http, 'http');
|
|
336
|
-
log("â
Patch Applied: http/https");
|
|
337
|
-
|
|
338
|
-
isPatched = true;
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// --- MAIN CLASS EXPORT ---
|
|
342
|
-
|
|
343
|
-
class Dropout {
|
|
344
|
-
constructor(config = {}) {
|
|
345
|
-
if (instance) return instance;
|
|
346
|
-
|
|
347
|
-
if (!config.apiKey || !config.projectId) {
|
|
348
|
-
console.warn("[Dropout] Missing apiKey or projectId. Tracking disabled.");
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
// Update Global Configuration
|
|
353
|
-
activeConfig.apiKey = config.apiKey;
|
|
354
|
-
activeConfig.projectId = config.projectId;
|
|
355
|
-
if (config.privacyMode) activeConfig.privacyMode = config.privacyMode;
|
|
356
|
-
activeConfig.debug = config.debug || false;
|
|
357
|
-
|
|
358
|
-
// Initialize Identity
|
|
359
|
-
if (!GLOBAL_OBJ.__dropout_session_id__) {
|
|
360
|
-
GLOBAL_OBJ.__dropout_session_id__ = generateSessionId();
|
|
361
|
-
}
|
|
362
292
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
instance = this;
|
|
293
|
+
patchNodeRequest(https, 'https');
|
|
294
|
+
patchNodeRequest(http, 'http');
|
|
295
|
+
this.log("â
Patch Applied: http/https");
|
|
367
296
|
}
|
|
368
297
|
}
|
|
369
298
|
|
|
370
|
-
// Auto-Start
|
|
299
|
+
// Auto-Start (Node Preload)
|
|
371
300
|
if (process.env.DROPOUT_PROJECT_ID && process.env.DROPOUT_API_KEY) {
|
|
372
301
|
new Dropout({
|
|
373
302
|
projectId: process.env.DROPOUT_PROJECT_ID,
|