@dropout-ai/runtime 0.2.2 → 0.2.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 +135 -27
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -7,17 +7,57 @@ const sessionId =
|
|
|
7
7
|
global.__dropout_session_id__ ||
|
|
8
8
|
(global.__dropout_session_id__ = crypto.randomUUID());
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
const NEGATIVE_FEEDBACK = [
|
|
11
|
+
"not helpful",
|
|
12
|
+
"dont just dump",
|
|
13
|
+
"you are worst",
|
|
14
|
+
"you are stupid",
|
|
15
|
+
"are you sure",
|
|
16
|
+
"i thought you are intelligent",
|
|
17
|
+
"let it be"
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const ADAPTATION_REQUESTS = [
|
|
21
|
+
"help me understand",
|
|
22
|
+
"explain better",
|
|
23
|
+
"dont just dump facts",
|
|
24
|
+
"in simple way",
|
|
25
|
+
"explain it properly"
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
let turnIndex = 0;
|
|
29
|
+
let lastPrompt = null;
|
|
30
|
+
|
|
31
|
+
function getSimilarity(text1, text2) {
|
|
32
|
+
if (!text1 || !text2) return 0;
|
|
33
|
+
const words1 = new Set(text1.toLowerCase().split(/\s+/));
|
|
34
|
+
const words2 = new Set(text2.toLowerCase().split(/\s+/));
|
|
35
|
+
const intersection = new Set([...words1].filter(x => words2.has(x)));
|
|
36
|
+
return intersection.size / Math.min(words1.size, words2.size);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getIntentHash(text) {
|
|
40
|
+
if (!text) return null;
|
|
41
|
+
return crypto.createHash('sha256').update(text.toLowerCase().trim()).digest('hex');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const CAPABILITY_LIMITATIONS = [
|
|
45
|
+
"i cannot access", "i don't have access", "i do not have access",
|
|
46
|
+
"cannot browse", "real-time information", "as an ai", "my knowledge cutoff",
|
|
47
|
+
"i'm sorry, but i don't", "i am sorry, but i don't"
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const CHALLENGE_PATTERNS = ["but ", "why ", "how ", "cant you", "can't you", "are you sure", "openai", "chatgpt"];
|
|
51
|
+
|
|
52
|
+
function detectSignals(text, output = "") {
|
|
53
|
+
const t = (text || "").toLowerCase();
|
|
54
|
+
const o = (output || "").toLowerCase();
|
|
55
|
+
return {
|
|
56
|
+
negativeFeedback: NEGATIVE_FEEDBACK.some(p => t.includes(p)) ? 1 : 0,
|
|
57
|
+
adaptationRequest: ADAPTATION_REQUESTS.some(p => t.includes(p)) ? 1 : 0,
|
|
58
|
+
userChallenge: CHALLENGE_PATTERNS.some(p => t.includes(p)) ? 1 : 0,
|
|
59
|
+
capabilityLimitation: CAPABILITY_LIMITATIONS.some(p => o.includes(p)) ? 1 : 0
|
|
60
|
+
};
|
|
21
61
|
}
|
|
22
62
|
|
|
23
63
|
(function () {
|
|
@@ -31,7 +71,9 @@ async function safeSendTelemetry(payload) {
|
|
|
31
71
|
version: 1,
|
|
32
72
|
captureOutput: true,
|
|
33
73
|
maxOutputBytes: 20000,
|
|
34
|
-
enabledProviders: ['openai']
|
|
74
|
+
enabledProviders: ['openai'],
|
|
75
|
+
privacyMode: 'safe', // 'safe' | 'full'
|
|
76
|
+
captureContent: false
|
|
35
77
|
};
|
|
36
78
|
|
|
37
79
|
// 3. Remote Config Fetch (Non-blocking, fire-and-forget)
|
|
@@ -96,41 +138,57 @@ async function safeSendTelemetry(payload) {
|
|
|
96
138
|
const isStream = contentType.includes('text/event-stream');
|
|
97
139
|
|
|
98
140
|
// 7. Safe Body Capture (Best effort)
|
|
99
|
-
let
|
|
141
|
+
let promptText = undefined;
|
|
100
142
|
let model = undefined;
|
|
101
143
|
|
|
102
144
|
if (init && typeof init.body === 'string') {
|
|
103
|
-
|
|
145
|
+
promptText = init.body;
|
|
104
146
|
try {
|
|
105
|
-
const bodyJson = JSON.parse(
|
|
147
|
+
const bodyJson = JSON.parse(promptText);
|
|
106
148
|
model = bodyJson.model;
|
|
107
149
|
} catch (e) { }
|
|
108
150
|
}
|
|
109
151
|
|
|
110
|
-
let
|
|
152
|
+
let outputText = undefined;
|
|
111
153
|
if (!isStream && config.captureOutput) {
|
|
112
154
|
try {
|
|
113
155
|
const cloned = response.clone();
|
|
114
|
-
|
|
156
|
+
outputText = await cloned.text();
|
|
115
157
|
// 8. Hard Capping (Infra Rule: Trust but verify)
|
|
116
|
-
if (
|
|
117
|
-
|
|
158
|
+
if (outputText && outputText.length > config.maxOutputBytes) {
|
|
159
|
+
outputText = outputText.slice(0, config.maxOutputBytes);
|
|
118
160
|
}
|
|
119
161
|
} catch (e) { }
|
|
120
162
|
}
|
|
121
163
|
|
|
164
|
+
const currentSignals = detectSignals(promptText, outputText);
|
|
165
|
+
const similarity = getSimilarity(lastPrompt, promptText);
|
|
166
|
+
const intentHash = getIntentHash(promptText);
|
|
167
|
+
|
|
168
|
+
const isSafe = config.privacyMode === 'safe' && !config.captureContent;
|
|
169
|
+
|
|
122
170
|
const payload = {
|
|
123
171
|
provider: url.includes('openai.com') ? 'openai' : 'unknown',
|
|
124
172
|
confidence: matchesKnownProvider(url) ? 'high' : 'heuristic',
|
|
125
173
|
url,
|
|
126
174
|
model,
|
|
127
|
-
prompt,
|
|
128
|
-
output,
|
|
129
175
|
latency_ms: latency,
|
|
130
176
|
timestamp: Math.floor(Date.now() / 1000),
|
|
131
|
-
session_id: sessionId
|
|
177
|
+
session_id: sessionId,
|
|
178
|
+
turn_index: turnIndex++,
|
|
179
|
+
intent_hash: intentHash,
|
|
180
|
+
similarity_prev: similarity,
|
|
181
|
+
negative_feedback: currentSignals.negativeFeedback,
|
|
182
|
+
adaptation_request: currentSignals.adaptationRequest,
|
|
183
|
+
user_challenge: currentSignals.userChallenge,
|
|
184
|
+
capability_limitation: currentSignals.capabilityLimitation,
|
|
185
|
+
// Redact content if in safe mode
|
|
186
|
+
prompt: isSafe ? undefined : promptText,
|
|
187
|
+
output: isSafe ? undefined : outputText,
|
|
132
188
|
};
|
|
133
189
|
|
|
190
|
+
lastPrompt = promptText;
|
|
191
|
+
|
|
134
192
|
// 9. Fire-and-forget
|
|
135
193
|
setTimeout(() => safeSendTelemetry(payload), 0);
|
|
136
194
|
}
|
|
@@ -146,24 +204,72 @@ async function safeSendTelemetry(payload) {
|
|
|
146
204
|
async function capture(target, options = {}) {
|
|
147
205
|
const start = Date.now();
|
|
148
206
|
|
|
207
|
+
// Support dropout.capture({ prompt, response, privacy: "full" })
|
|
208
|
+
if (typeof target === 'object' && target !== null && !target.then) {
|
|
209
|
+
const { prompt, response, privacy } = target;
|
|
210
|
+
const isSafe = (privacy || 'safe') === 'safe';
|
|
211
|
+
|
|
212
|
+
const currentSignals = detectSignals(prompt, response);
|
|
213
|
+
const similarity = getSimilarity(lastPrompt, prompt);
|
|
214
|
+
const intentHash = getIntentHash(prompt);
|
|
215
|
+
|
|
216
|
+
const payload = {
|
|
217
|
+
provider: 'manual-opt-in',
|
|
218
|
+
prompt: isSafe ? undefined : prompt,
|
|
219
|
+
output: isSafe ? undefined : response,
|
|
220
|
+
latency_ms: 0,
|
|
221
|
+
timestamp: Math.floor(Date.now() / 1000),
|
|
222
|
+
session_id: sessionId,
|
|
223
|
+
turn_index: turnIndex++,
|
|
224
|
+
intent_hash: intentHash,
|
|
225
|
+
similarity_prev: similarity,
|
|
226
|
+
negative_feedback: currentSignals.negativeFeedback,
|
|
227
|
+
adaptation_request: currentSignals.adaptationRequest,
|
|
228
|
+
user_challenge: currentSignals.userChallenge,
|
|
229
|
+
capability_limitation: currentSignals.capabilityLimitation,
|
|
230
|
+
mode: 'manual'
|
|
231
|
+
};
|
|
232
|
+
lastPrompt = prompt;
|
|
233
|
+
setTimeout(() => safeSendTelemetry(payload), 0);
|
|
234
|
+
return response;
|
|
235
|
+
}
|
|
236
|
+
|
|
149
237
|
try {
|
|
150
238
|
const result =
|
|
151
239
|
typeof target === 'function'
|
|
152
240
|
? await target()
|
|
153
241
|
: await Promise.resolve(target);
|
|
154
242
|
|
|
243
|
+
const isSafe = options.privacy !== 'full';
|
|
244
|
+
|
|
245
|
+
const promptText = options.prompt;
|
|
246
|
+
const resultText = typeof result === 'string'
|
|
247
|
+
? result
|
|
248
|
+
: JSON.stringify(result).slice(0, 20000);
|
|
249
|
+
|
|
250
|
+
const currentSignals = detectSignals(promptText, resultText);
|
|
251
|
+
const similarity = getSimilarity(lastPrompt, promptText);
|
|
252
|
+
const intentHash = getIntentHash(promptText);
|
|
253
|
+
|
|
155
254
|
const payload = {
|
|
156
255
|
provider: options.provider || 'manual',
|
|
157
256
|
model: options.model,
|
|
158
|
-
prompt:
|
|
159
|
-
output:
|
|
160
|
-
? result
|
|
161
|
-
: JSON.stringify(result).slice(0, 20000),
|
|
257
|
+
prompt: isSafe ? undefined : promptText,
|
|
258
|
+
output: isSafe ? undefined : resultText,
|
|
162
259
|
latency_ms: Date.now() - start,
|
|
163
260
|
timestamp: Math.floor(Date.now() / 1000),
|
|
261
|
+
session_id: sessionId,
|
|
262
|
+
turn_index: turnIndex++,
|
|
263
|
+
intent_hash: intentHash,
|
|
264
|
+
similarity_prev: similarity,
|
|
265
|
+
negative_feedback: currentSignals.negativeFeedback,
|
|
266
|
+
adaptation_request: currentSignals.adaptationRequest,
|
|
267
|
+
user_challenge: currentSignals.userChallenge,
|
|
268
|
+
capability_limitation: currentSignals.capabilityLimitation,
|
|
164
269
|
mode: 'manual'
|
|
165
270
|
};
|
|
166
271
|
|
|
272
|
+
lastPrompt = promptText;
|
|
167
273
|
setTimeout(() => safeSendTelemetry(payload), 0);
|
|
168
274
|
return result;
|
|
169
275
|
} catch (error) {
|
|
@@ -172,7 +278,9 @@ async function capture(target, options = {}) {
|
|
|
172
278
|
error: error?.message || String(error),
|
|
173
279
|
latency_ms: Date.now() - start,
|
|
174
280
|
timestamp: Math.floor(Date.now() / 1000),
|
|
175
|
-
mode: 'manual'
|
|
281
|
+
mode: 'manual',
|
|
282
|
+
session_id: sessionId,
|
|
283
|
+
turn_index: turnIndex++
|
|
176
284
|
};
|
|
177
285
|
|
|
178
286
|
setTimeout(() => safeSendTelemetry(payload), 0);
|