@dropout-ai/runtime 0.5.2 → 0.5.3
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 +66 -21
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Stability: High (Browser-Safe, Next.js Safe)
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
let https, http, crypto;
|
|
7
|
+
let https, http, crypto, AsyncLocalStorage;
|
|
8
8
|
|
|
9
9
|
// --- DEFAULT CONFIGURATION ---
|
|
10
10
|
const SUPABASE_FUNCTION_URL =
|
|
@@ -34,6 +34,7 @@ class Dropout {
|
|
|
34
34
|
|
|
35
35
|
// 🛡️ Browser Guard (prevents client execution)
|
|
36
36
|
if (typeof window !== "undefined" && typeof window.document !== "undefined") {
|
|
37
|
+
if (config.debug) console.warn("[Dropout] ⚠️ Skipped: Browser detected. SDK is Server-Only.");
|
|
37
38
|
return;
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -45,11 +46,16 @@ class Dropout {
|
|
|
45
46
|
return;
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
// 🛡️ Safe Dependency Import
|
|
48
50
|
try {
|
|
49
51
|
https = require("https");
|
|
50
52
|
http = require("http");
|
|
51
53
|
crypto = require("crypto");
|
|
54
|
+
// Import async_hooks dynamically to avoid browser build crashes
|
|
55
|
+
const asyncHooks = require("async_hooks");
|
|
56
|
+
AsyncLocalStorage = asyncHooks.AsyncLocalStorage;
|
|
52
57
|
} catch {
|
|
58
|
+
if (config.debug) console.warn("[Dropout] ⚠️ Node core modules missing.");
|
|
53
59
|
return;
|
|
54
60
|
}
|
|
55
61
|
|
|
@@ -64,13 +70,8 @@ class Dropout {
|
|
|
64
70
|
// ✅ ASYNC CONTEXT (LAZY, NODE-ONLY)
|
|
65
71
|
// --------------------------------------------------
|
|
66
72
|
this.storage = null;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const asyncHooks = require("async_hooks");
|
|
70
|
-
this.storage = new asyncHooks.AsyncLocalStorage();
|
|
71
|
-
} catch {
|
|
72
|
-
// Edge / Browser / Unsupported — safely disabled
|
|
73
|
-
this.storage = null;
|
|
73
|
+
if (AsyncLocalStorage) {
|
|
74
|
+
this.storage = new AsyncLocalStorage();
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
// --------------------------------------------------
|
|
@@ -112,6 +113,7 @@ class Dropout {
|
|
|
112
113
|
// CONTEXT API (OPTIONAL BUT SAFE)
|
|
113
114
|
// --------------------------------------------------
|
|
114
115
|
run(sessionId, callback) {
|
|
116
|
+
// If storage isn't available, just run the callback
|
|
115
117
|
if (!this.storage) return callback();
|
|
116
118
|
|
|
117
119
|
const context = {
|
|
@@ -127,11 +129,6 @@ class Dropout {
|
|
|
127
129
|
return this.storage.getStore() || null;
|
|
128
130
|
}
|
|
129
131
|
|
|
130
|
-
getSessionId() {
|
|
131
|
-
const ctx = this.getContext();
|
|
132
|
-
return ctx?.sessionId || "global_session";
|
|
133
|
-
}
|
|
134
|
-
|
|
135
132
|
// --------------------------------------------------
|
|
136
133
|
// NORMALIZATION
|
|
137
134
|
// --------------------------------------------------
|
|
@@ -191,10 +188,11 @@ class Dropout {
|
|
|
191
188
|
received_at: new Date().toISOString(),
|
|
192
189
|
};
|
|
193
190
|
|
|
194
|
-
const
|
|
191
|
+
const requestModule = this.captureEndpoint.startsWith("http:")
|
|
195
192
|
? http
|
|
196
|
-
: https
|
|
197
|
-
|
|
193
|
+
: https;
|
|
194
|
+
|
|
195
|
+
const req = requestModule.request(this.captureEndpoint, {
|
|
198
196
|
method: "POST",
|
|
199
197
|
headers: {
|
|
200
198
|
"Content-Type": "application/json",
|
|
@@ -209,7 +207,7 @@ class Dropout {
|
|
|
209
207
|
}
|
|
210
208
|
|
|
211
209
|
// --------------------------------------------------
|
|
212
|
-
// NETWORK PATCHING
|
|
210
|
+
// NETWORK PATCHING (INTELLIGENT)
|
|
213
211
|
// --------------------------------------------------
|
|
214
212
|
patchNetwork() {
|
|
215
213
|
const _this = this;
|
|
@@ -227,12 +225,14 @@ class Dropout {
|
|
|
227
225
|
}
|
|
228
226
|
|
|
229
227
|
let bodyStr = "";
|
|
228
|
+
let parsedBody = null;
|
|
230
229
|
if (init?.body) {
|
|
231
230
|
try {
|
|
232
231
|
bodyStr =
|
|
233
232
|
typeof init.body === "string"
|
|
234
233
|
? init.body
|
|
235
234
|
: JSON.stringify(init.body);
|
|
235
|
+
parsedBody = JSON.parse(bodyStr);
|
|
236
236
|
} catch { }
|
|
237
237
|
}
|
|
238
238
|
|
|
@@ -240,13 +240,58 @@ class Dropout {
|
|
|
240
240
|
return originalFetch.apply(this, arguments);
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
-
|
|
243
|
+
// 🕵️ 1. SESSION LOGIC (PRIORITY ORDER)
|
|
244
|
+
// A. Explicit Context (if wrapped in .run())
|
|
245
|
+
const ctx = _this.getContext();
|
|
246
|
+
let activeSessionId = ctx?.sessionId;
|
|
247
|
+
|
|
248
|
+
if (!activeSessionId && parsedBody) {
|
|
249
|
+
// B. Explicit Override in Payload
|
|
250
|
+
const explicitId =
|
|
251
|
+
parsedBody.session_id ||
|
|
252
|
+
parsedBody.metadata?.session_id ||
|
|
253
|
+
parsedBody.metadata?.sessionId;
|
|
254
|
+
|
|
255
|
+
if (explicitId) {
|
|
256
|
+
activeSessionId = explicitId;
|
|
257
|
+
} else {
|
|
258
|
+
// C. SMART FINGERPRINTING (The Fix)
|
|
259
|
+
const userId =
|
|
260
|
+
parsedBody.user ||
|
|
261
|
+
parsedBody.userId ||
|
|
262
|
+
parsedBody.metadata?.user_id ||
|
|
263
|
+
"anon_user";
|
|
264
|
+
|
|
265
|
+
let rootContext = "";
|
|
266
|
+
// Grab first 64 chars of the FIRST message as "Conversation Anchor"
|
|
267
|
+
if (parsedBody.messages?.length > 0) {
|
|
268
|
+
rootContext = parsedBody.messages[0].content?.slice(0, 64) || "";
|
|
269
|
+
} else if (parsedBody.prompt) {
|
|
270
|
+
rootContext =
|
|
271
|
+
typeof parsedBody.prompt === "string"
|
|
272
|
+
? parsedBody.prompt.slice(0, 64)
|
|
273
|
+
: JSON.stringify(parsedBody.prompt).slice(0, 64);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (rootContext) {
|
|
277
|
+
// Salt with Date (YYYY-MM-DD) to differentiate sessions across days
|
|
278
|
+
const dateSalt = new Date().toISOString().slice(0, 10);
|
|
279
|
+
activeSessionId =
|
|
280
|
+
"sess_" +
|
|
281
|
+
_this.hash(`${userId}::${rootContext}::${dateSalt}`).substring(0, 12);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// D. Fallback
|
|
287
|
+
activeSessionId = activeSessionId || _this.generateSessionId();
|
|
288
|
+
|
|
244
289
|
const { provider, model } = _this.normalize(url, bodyStr);
|
|
245
290
|
const pHash = _this.hash(bodyStr);
|
|
246
291
|
const start = Date.now();
|
|
247
292
|
|
|
248
293
|
_this.emit({
|
|
249
|
-
session_id:
|
|
294
|
+
session_id: activeSessionId,
|
|
250
295
|
turn_index: 0,
|
|
251
296
|
turn_role: "user",
|
|
252
297
|
provider,
|
|
@@ -266,7 +311,7 @@ class Dropout {
|
|
|
266
311
|
}
|
|
267
312
|
|
|
268
313
|
_this.emit({
|
|
269
|
-
session_id:
|
|
314
|
+
session_id: activeSessionId,
|
|
270
315
|
turn_index: 1,
|
|
271
316
|
turn_role: "assistant",
|
|
272
317
|
latency_ms: Date.now() - start,
|
|
@@ -296,4 +341,4 @@ if (
|
|
|
296
341
|
}
|
|
297
342
|
|
|
298
343
|
Dropout.default = Dropout;
|
|
299
|
-
module.exports = Dropout;
|
|
344
|
+
module.exports = Dropout;
|