@dropout-ai/runtime 0.3.6 â 0.3.8
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 +212 -182
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @dropout-ai/runtime
|
|
3
3
|
* Universal AI Interaction Capture for Node.js
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Stability: High (Browser-Safe, Silent Failures)
|
|
5
|
+
* Features: Auto-Discovery, Connectivity Check, Dual-Schema Support
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
const http = require('http');
|
|
10
|
-
const crypto = require('crypto');
|
|
8
|
+
let https, http, crypto;
|
|
11
9
|
|
|
12
10
|
// --- DEFAULT CONFIGURATION ---
|
|
13
11
|
const SUPABASE_FUNCTION_URL = "https://hipughmjlwmwjxzyxfzs.supabase.co/functions/v1/capture-sealed";
|
|
@@ -20,52 +18,74 @@ const KNOWN_AI_DOMAINS = [
|
|
|
20
18
|
|
|
21
19
|
class Dropout {
|
|
22
20
|
constructor(config = {}) {
|
|
23
|
-
//
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
// đĄī¸ 1. BROWSER GUARD (Client-Side Protection)
|
|
22
|
+
// If this accidentally runs in a browser/Client Component, we abort silently.
|
|
23
|
+
if (typeof window !== 'undefined' && typeof window.document !== 'undefined') {
|
|
24
|
+
if (config.debug) console.warn("[Dropout] â ī¸ Skipped: Browser detected. SDK is Server-Only.");
|
|
26
25
|
return;
|
|
27
26
|
}
|
|
28
27
|
|
|
29
|
-
//
|
|
28
|
+
// đĄī¸ 2. CREDENTIAL CHECK
|
|
30
29
|
if (!config.apiKey || !config.projectId) {
|
|
31
|
-
console.warn("[Dropout] â ī¸
|
|
30
|
+
if (config.debug) console.warn("[Dropout] â ī¸ Skipped: Missing API Key or Project ID.");
|
|
32
31
|
return;
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
this.maxOutputBytes = 32768;
|
|
43
|
-
|
|
44
|
-
// 3. Conversation State (Matches your snippet)
|
|
45
|
-
this.turnIndex = 0;
|
|
46
|
-
this.lastTurnConfirmed = true;
|
|
47
|
-
this.lastPromptHash = null;
|
|
48
|
-
this.lastResponseHash = null;
|
|
49
|
-
|
|
50
|
-
// 4. Singleton Guard
|
|
51
|
-
if (global.__dropout_initialized__) {
|
|
52
|
-
if (this.debug) console.log("[Dropout] âšī¸ Already initialized. Skipping patch.");
|
|
34
|
+
// đĄī¸ 3. DEPENDENCY CHECK
|
|
35
|
+
try {
|
|
36
|
+
https = require('https');
|
|
37
|
+
http = require('http');
|
|
38
|
+
crypto = require('crypto');
|
|
39
|
+
} catch (e) {
|
|
40
|
+
if (config.debug) console.warn("[Dropout] â ī¸ Skipped: Node core modules missing.");
|
|
53
41
|
return;
|
|
54
42
|
}
|
|
55
|
-
global.__dropout_initialized__ = true;
|
|
56
43
|
|
|
57
|
-
//
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
44
|
+
// --- INITIALIZATION ---
|
|
45
|
+
try {
|
|
46
|
+
this.config = config;
|
|
47
|
+
this.projectId = config.projectId;
|
|
48
|
+
this.apiKey = config.apiKey;
|
|
49
|
+
this.debug = config.debug || false;
|
|
50
|
+
this.privacy = config.privacy || 'full';
|
|
51
|
+
this.captureEndpoint = SUPABASE_FUNCTION_URL;
|
|
52
|
+
this.maxOutputBytes = 32768;
|
|
53
|
+
|
|
54
|
+
// State
|
|
55
|
+
this.turnIndex = 0;
|
|
56
|
+
this.lastTurnConfirmed = true;
|
|
57
|
+
this.lastPromptHash = null;
|
|
58
|
+
this.lastResponseHash = null;
|
|
59
|
+
|
|
60
|
+
// Singleton Guard
|
|
61
|
+
if (global.__dropout_initialized__) {
|
|
62
|
+
if (this.debug) console.log("[Dropout] âšī¸ Already initialized.");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
global.__dropout_initialized__ = true;
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
// Identity
|
|
68
|
+
if (!global.__dropout_session_id__) {
|
|
69
|
+
global.__dropout_session_id__ = this.generateSessionId();
|
|
70
|
+
}
|
|
64
71
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
// Bindings
|
|
73
|
+
this.log = this.log.bind(this);
|
|
74
|
+
this.emit = this.emit.bind(this);
|
|
75
|
+
|
|
76
|
+
// đĄī¸ 4. SAFE STARTUP
|
|
77
|
+
this.patchNetwork();
|
|
78
|
+
|
|
79
|
+
// đ 5. CONNECTION VERIFICATION (Async Ping)
|
|
80
|
+
// Only runs if debug is TRUE to confirm setup
|
|
81
|
+
if (this.debug) {
|
|
82
|
+
this.validateConnection();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
} catch (err) {
|
|
86
|
+
// THE ULTIMATE CATCH-ALL: Ensure app NEVER crashes
|
|
87
|
+
if (config.debug) console.error("[Dropout] â Initialization Error:", err);
|
|
88
|
+
}
|
|
69
89
|
}
|
|
70
90
|
|
|
71
91
|
// --- UTILS ---
|
|
@@ -81,6 +101,39 @@ class Dropout {
|
|
|
81
101
|
}
|
|
82
102
|
}
|
|
83
103
|
|
|
104
|
+
// --- HEALTH CHECK ---
|
|
105
|
+
validateConnection() {
|
|
106
|
+
this.log("đ Verifying connection to Dropout Cloud...");
|
|
107
|
+
|
|
108
|
+
const req = https.request(this.captureEndpoint, {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
headers: {
|
|
111
|
+
'Content-Type': 'application/json',
|
|
112
|
+
'x-dropout-key': this.apiKey
|
|
113
|
+
}
|
|
114
|
+
}, (res) => {
|
|
115
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
116
|
+
console.log(`[Dropout] â
Connected to Project: ${this.projectId}`);
|
|
117
|
+
} else {
|
|
118
|
+
console.warn(`[Dropout] â ī¸ Connection Failed (Status: ${res.statusCode}). Check your API Key.`);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
req.on('error', (e) => console.warn(`[Dropout] â Connection Error: ${e.message}`));
|
|
123
|
+
|
|
124
|
+
// Send lightweight Ping payload
|
|
125
|
+
req.write(JSON.stringify({
|
|
126
|
+
project_id: this.projectId,
|
|
127
|
+
session_id: 'system_ping',
|
|
128
|
+
turn_role: 'system',
|
|
129
|
+
turn_index: 0,
|
|
130
|
+
content: 'ping',
|
|
131
|
+
//content_blob: 'ping',
|
|
132
|
+
metadata_flags: { type: 'connectivity_check' }
|
|
133
|
+
}));
|
|
134
|
+
req.end();
|
|
135
|
+
}
|
|
136
|
+
|
|
84
137
|
hash(text) {
|
|
85
138
|
if (!text) return null;
|
|
86
139
|
try {
|
|
@@ -91,35 +144,30 @@ class Dropout {
|
|
|
91
144
|
normalize(url, body) {
|
|
92
145
|
let provider = 'custom';
|
|
93
146
|
let model = 'unknown';
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (body) {
|
|
107
|
-
try {
|
|
147
|
+
try {
|
|
148
|
+
if (url) {
|
|
149
|
+
const u = url.toLowerCase();
|
|
150
|
+
if (u.includes('openai')) provider = 'openai';
|
|
151
|
+
else if (u.includes('anthropic')) provider = 'anthropic';
|
|
152
|
+
else if (u.includes('google') || u.includes('gemini') || u.includes('aiplatform')) provider = 'google';
|
|
153
|
+
else if (u.includes('groq')) provider = 'groq';
|
|
154
|
+
else if (u.includes('mistral')) provider = 'mistral';
|
|
155
|
+
else if (u.includes('cohere')) provider = 'cohere';
|
|
156
|
+
else if (u.includes('localhost') || u.includes('127.0.0.1')) provider = 'local';
|
|
157
|
+
}
|
|
158
|
+
if (body) {
|
|
108
159
|
const parsed = typeof body === 'string' ? JSON.parse(body) : body;
|
|
109
160
|
if (parsed.model) model = parsed.model;
|
|
110
|
-
// Simple heuristic fallback if model is missing but structure looks AI-ish
|
|
111
161
|
if (provider === 'custom' && (parsed.messages || parsed.prompt)) provider = 'heuristic';
|
|
112
|
-
}
|
|
113
|
-
}
|
|
162
|
+
}
|
|
163
|
+
} catch (e) { /* Ignore parsing errors */ }
|
|
114
164
|
return { provider, model };
|
|
115
165
|
}
|
|
116
166
|
|
|
117
167
|
isAiRequest(url, bodyString) {
|
|
118
168
|
if (!url) return false;
|
|
119
169
|
if (url.includes(this.captureEndpoint)) return false;
|
|
120
|
-
|
|
121
170
|
if (KNOWN_AI_DOMAINS.some(d => url.includes(d))) return true;
|
|
122
|
-
|
|
123
171
|
if (bodyString) {
|
|
124
172
|
return (bodyString.includes('"model"') || bodyString.includes('"messages"')) &&
|
|
125
173
|
(bodyString.includes('"user"') || bodyString.includes('"prompt"'));
|
|
@@ -129,43 +177,40 @@ class Dropout {
|
|
|
129
177
|
|
|
130
178
|
// --- EMITTER ---
|
|
131
179
|
emit(payload) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
content_hash: payload.content_hash,
|
|
150
|
-
|
|
151
|
-
// Metadata
|
|
152
|
-
metadata_flags: payload.metadata_flags,
|
|
153
|
-
received_at: new Date().toISOString()
|
|
154
|
-
};
|
|
180
|
+
try {
|
|
181
|
+
if (!this.apiKey || !this.projectId) return;
|
|
182
|
+
|
|
183
|
+
const finalPayload = {
|
|
184
|
+
project_id: this.projectId,
|
|
185
|
+
session_id: payload.session_id,
|
|
186
|
+
turn_index: payload.turn_index,
|
|
187
|
+
turn_role: payload.turn_role,
|
|
188
|
+
provider: payload.provider,
|
|
189
|
+
model: payload.model,
|
|
190
|
+
latency_ms: payload.latency_ms || null,
|
|
191
|
+
content: payload.content,
|
|
192
|
+
//content_blob: payload.content,
|
|
193
|
+
content_hash: payload.content_hash,
|
|
194
|
+
metadata_flags: payload.metadata_flags,
|
|
195
|
+
received_at: new Date().toISOString()
|
|
196
|
+
};
|
|
155
197
|
|
|
156
|
-
|
|
198
|
+
this.log(`đ Sending Capture [${payload.turn_role}]`);
|
|
157
199
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
200
|
+
const req = https.request(this.captureEndpoint, {
|
|
201
|
+
method: 'POST',
|
|
202
|
+
headers: {
|
|
203
|
+
'Content-Type': 'application/json',
|
|
204
|
+
'x-dropout-key': this.apiKey
|
|
205
|
+
}
|
|
206
|
+
});
|
|
165
207
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
208
|
+
req.on('error', (e) => this.log("â Upload Failed (Silent)", e.message));
|
|
209
|
+
req.write(JSON.stringify(finalPayload));
|
|
210
|
+
req.end();
|
|
211
|
+
} catch (e) {
|
|
212
|
+
this.log("â ī¸ Emit Error (Silent)", e);
|
|
213
|
+
}
|
|
169
214
|
}
|
|
170
215
|
|
|
171
216
|
// --- CORE PATCHING ---
|
|
@@ -176,14 +221,14 @@ class Dropout {
|
|
|
176
221
|
if (global.fetch) {
|
|
177
222
|
const originalFetch = global.fetch;
|
|
178
223
|
global.fetch = async function (input, init) {
|
|
179
|
-
|
|
224
|
+
let url;
|
|
225
|
+
try { url = typeof input === 'string' ? input : input?.url; } catch (e) { return originalFetch.apply(this, arguments); }
|
|
180
226
|
|
|
181
227
|
let bodyStr = "";
|
|
182
228
|
if (init && init.body) {
|
|
183
229
|
try { bodyStr = typeof init.body === 'string' ? init.body : JSON.stringify(init.body); } catch (e) { }
|
|
184
230
|
}
|
|
185
231
|
|
|
186
|
-
// 1. Guard
|
|
187
232
|
if (!_this.isAiRequest(url, bodyStr)) {
|
|
188
233
|
return originalFetch.apply(this, arguments);
|
|
189
234
|
}
|
|
@@ -191,7 +236,6 @@ class Dropout {
|
|
|
191
236
|
_this.log(`⥠[FETCH] Intercepting: ${url}`);
|
|
192
237
|
const start = Date.now();
|
|
193
238
|
|
|
194
|
-
// 2. Determine Turn Logic (Your Snippet)
|
|
195
239
|
let activeTurn;
|
|
196
240
|
if (_this.lastTurnConfirmed) {
|
|
197
241
|
activeTurn = _this.turnIndex++;
|
|
@@ -203,7 +247,6 @@ class Dropout {
|
|
|
203
247
|
const { provider, model } = _this.normalize(url, bodyStr);
|
|
204
248
|
const pHash = _this.hash(bodyStr);
|
|
205
249
|
|
|
206
|
-
// 3. Emit Request (User)
|
|
207
250
|
_this.emit({
|
|
208
251
|
session_id: global.__dropout_session_id__,
|
|
209
252
|
turn_index: activeTurn,
|
|
@@ -212,15 +255,10 @@ class Dropout {
|
|
|
212
255
|
model,
|
|
213
256
|
content: _this.privacy === 'full' ? bodyStr : null,
|
|
214
257
|
content_hash: pHash,
|
|
215
|
-
metadata_flags: {
|
|
216
|
-
retry_like: pHash === _this.lastPromptHash ? 1 : 0,
|
|
217
|
-
method: 'FETCH',
|
|
218
|
-
url: url
|
|
219
|
-
}
|
|
258
|
+
metadata_flags: { retry_like: pHash === _this.lastPromptHash ? 1 : 0, method: 'FETCH', url: url }
|
|
220
259
|
});
|
|
221
260
|
_this.lastPromptHash = pHash;
|
|
222
261
|
|
|
223
|
-
// 4. Actual Network Call
|
|
224
262
|
let response;
|
|
225
263
|
try {
|
|
226
264
|
response = await originalFetch.apply(this, arguments);
|
|
@@ -228,13 +266,10 @@ class Dropout {
|
|
|
228
266
|
|
|
229
267
|
const latency = Date.now() - start;
|
|
230
268
|
|
|
231
|
-
// 5. Emit Response (Assistant)
|
|
232
269
|
try {
|
|
233
270
|
const cloned = response.clone();
|
|
234
271
|
let oText = await cloned.text();
|
|
235
|
-
if (oText && oText.length > _this.maxOutputBytes)
|
|
236
|
-
oText = oText.slice(0, _this.maxOutputBytes);
|
|
237
|
-
}
|
|
272
|
+
if (oText && oText.length > _this.maxOutputBytes) oText = oText.slice(0, _this.maxOutputBytes);
|
|
238
273
|
const oHash = _this.hash(oText);
|
|
239
274
|
|
|
240
275
|
_this.emit({
|
|
@@ -266,15 +301,17 @@ class Dropout {
|
|
|
266
301
|
const originalRequest = module.request;
|
|
267
302
|
module.request = function (...args) {
|
|
268
303
|
let url;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
304
|
+
try {
|
|
305
|
+
if (typeof args[0] === 'string') {
|
|
306
|
+
url = args[0];
|
|
307
|
+
} else {
|
|
308
|
+
const opts = args[0] || {};
|
|
309
|
+
const protocol = opts.protocol || (moduleName === 'https' ? 'https:' : 'http:');
|
|
310
|
+
const host = opts.hostname || opts.host || 'localhost';
|
|
311
|
+
const path = opts.path || '/';
|
|
312
|
+
url = `${protocol}//${host}${path}`;
|
|
313
|
+
}
|
|
314
|
+
} catch (e) { return originalRequest.apply(this, args); }
|
|
278
315
|
|
|
279
316
|
if (!_this.isAiRequest(url, null)) {
|
|
280
317
|
return originalRequest.apply(this, args);
|
|
@@ -288,88 +325,81 @@ class Dropout {
|
|
|
288
325
|
const originalWrite = clientRequest.write;
|
|
289
326
|
const originalEnd = clientRequest.end;
|
|
290
327
|
|
|
291
|
-
// Buffer Request Data
|
|
292
328
|
clientRequest.write = function (...writeArgs) {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
329
|
+
try {
|
|
330
|
+
const chunk = writeArgs[0];
|
|
331
|
+
if (chunk && (typeof chunk === 'string' || Buffer.isBuffer(chunk))) {
|
|
332
|
+
reqChunks.push(Buffer.from(chunk));
|
|
333
|
+
}
|
|
334
|
+
} catch (e) { }
|
|
297
335
|
return originalWrite.apply(this, writeArgs);
|
|
298
336
|
};
|
|
299
337
|
|
|
300
338
|
clientRequest.end = function (...endArgs) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
339
|
+
try {
|
|
340
|
+
const chunk = endArgs[0];
|
|
341
|
+
if (chunk && (typeof chunk === 'string' || Buffer.isBuffer(chunk))) {
|
|
342
|
+
reqChunks.push(Buffer.from(chunk));
|
|
343
|
+
}
|
|
305
344
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
const pHash = _this.hash(reqBody);
|
|
345
|
+
const reqBody = Buffer.concat(reqChunks).toString('utf8');
|
|
346
|
+
const { provider, model } = _this.normalize(url, reqBody);
|
|
347
|
+
const pHash = _this.hash(reqBody);
|
|
310
348
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
349
|
+
let activeTurn;
|
|
350
|
+
if (_this.lastTurnConfirmed) {
|
|
351
|
+
activeTurn = _this.turnIndex++;
|
|
352
|
+
_this.lastTurnConfirmed = false;
|
|
353
|
+
} else {
|
|
354
|
+
activeTurn = _this.turnIndex > 0 ? _this.turnIndex - 1 : 0;
|
|
355
|
+
}
|
|
319
356
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
clientRequest._dropout_model = model;
|
|
357
|
+
clientRequest._dropout_turn = activeTurn;
|
|
358
|
+
clientRequest._dropout_provider = provider;
|
|
359
|
+
clientRequest._dropout_model = model;
|
|
324
360
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
_this.lastPromptHash = pHash;
|
|
361
|
+
_this.emit({
|
|
362
|
+
session_id: global.__dropout_session_id__,
|
|
363
|
+
turn_index: activeTurn,
|
|
364
|
+
turn_role: 'user',
|
|
365
|
+
provider,
|
|
366
|
+
model,
|
|
367
|
+
content: _this.privacy === 'full' ? reqBody : null,
|
|
368
|
+
content_hash: pHash,
|
|
369
|
+
metadata_flags: { retry_like: pHash === _this.lastPromptHash ? 1 : 0, method: moduleName.toUpperCase(), url: url }
|
|
370
|
+
});
|
|
371
|
+
_this.lastPromptHash = pHash;
|
|
372
|
+
} catch (e) { _this.log("â ī¸ Request Capture Failed"); }
|
|
340
373
|
|
|
341
374
|
return originalEnd.apply(this, endArgs);
|
|
342
375
|
};
|
|
343
376
|
|
|
344
|
-
// --- EMIT RESPONSE ---
|
|
345
377
|
clientRequest.on('response', (res) => {
|
|
346
378
|
const resChunks = [];
|
|
347
379
|
res.on('data', (chunk) => resChunks.push(chunk));
|
|
348
380
|
res.on('end', () => {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
resBody = resBody.slice(0, _this.maxOutputBytes);
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
_this.lastResponseHash = oHash;
|
|
372
|
-
_this.lastTurnConfirmed = true;
|
|
381
|
+
try {
|
|
382
|
+
let resBody = Buffer.concat(resChunks).toString('utf8');
|
|
383
|
+
if (resBody && resBody.length > _this.maxOutputBytes) resBody = resBody.slice(0, _this.maxOutputBytes);
|
|
384
|
+
|
|
385
|
+
const latency = Date.now() - start;
|
|
386
|
+
const oHash = _this.hash(resBody);
|
|
387
|
+
|
|
388
|
+
_this.emit({
|
|
389
|
+
session_id: global.__dropout_session_id__,
|
|
390
|
+
turn_index: clientRequest._dropout_turn || 0,
|
|
391
|
+
turn_role: 'assistant',
|
|
392
|
+
latency_ms: latency,
|
|
393
|
+
provider: clientRequest._dropout_provider,
|
|
394
|
+
model: clientRequest._dropout_model,
|
|
395
|
+
content: _this.privacy === 'full' ? resBody : null,
|
|
396
|
+
content_hash: oHash,
|
|
397
|
+
metadata_flags: { status: res.statusCode, non_adaptive_response: oHash === _this.lastResponseHash ? 1 : 0 }
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
_this.lastResponseHash = oHash;
|
|
401
|
+
_this.lastTurnConfirmed = true;
|
|
402
|
+
} catch (e) { _this.log("â ī¸ Response Capture Failed"); }
|
|
373
403
|
});
|
|
374
404
|
});
|
|
375
405
|
|
|
@@ -383,8 +413,8 @@ class Dropout {
|
|
|
383
413
|
}
|
|
384
414
|
}
|
|
385
415
|
|
|
386
|
-
// Auto-Start (
|
|
387
|
-
if (process.env.DROPOUT_PROJECT_ID && process.env.DROPOUT_API_KEY) {
|
|
416
|
+
// Auto-Start (Server-Side Only)
|
|
417
|
+
if (typeof process !== 'undefined' && process.env.DROPOUT_PROJECT_ID && process.env.DROPOUT_API_KEY) {
|
|
388
418
|
new Dropout({
|
|
389
419
|
projectId: process.env.DROPOUT_PROJECT_ID,
|
|
390
420
|
apiKey: process.env.DROPOUT_API_KEY,
|