@dropout-ai/runtime 0.1.0
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 +36 -0
- package/package.json +28 -0
- package/src/index.js +101 -0
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @dropout-ai/runtime
|
|
2
|
+
|
|
3
|
+
The invisible Node.js sensor for AI observability.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`@dropout-ai/runtime` is a zero-dependency, fire-and-forget telemetry sensor that automatically captures OpenAI API interactions from your Node.js or Next.js application.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @dropout-ai/runtime
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
Simply import the package at the very top of your application's entry point (e.g., `index.js` or `instrumentation.ts`):
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
import "@dropout-ai/runtime";
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Once imported, it automatically:
|
|
24
|
+
1. Patches `global.fetch`.
|
|
25
|
+
2. Detects OpenAI API calls.
|
|
26
|
+
3. Reports telemetry to your local Dropout backend (defaulting to `http://localhost:3000/capture`).
|
|
27
|
+
4. Fails gracefully and silently without affecting your production traffic.
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
- **Zero Configuration**: Just import and it works.
|
|
31
|
+
- **Invisible capture**: No code changes to your AI logic.
|
|
32
|
+
- **Non-blocking**: Uses fire-and-forget network calls.
|
|
33
|
+
- **Remote Config**: Supports dynamic output capping and provider filtering.
|
|
34
|
+
|
|
35
|
+
## License
|
|
36
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dropout-ai/runtime",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Invisible Node.js runtime for capturing AI interactions.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "node test.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"openai",
|
|
11
|
+
"telemetry",
|
|
12
|
+
"runtime",
|
|
13
|
+
"invisible"
|
|
14
|
+
],
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/vaibhavv4re/dropout.git",
|
|
18
|
+
"directory": "packages/runtime"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"src"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"author": "Dropout Team",
|
|
27
|
+
"license": "MIT"
|
|
28
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
// 1. Guard global.fetch existence and idempotency
|
|
3
|
+
if (typeof global.fetch !== 'function' || global.fetch.__dropout_patched__) {
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const originalFetch = global.fetch;
|
|
8
|
+
|
|
9
|
+
// 2. Default Configuration
|
|
10
|
+
let config = {
|
|
11
|
+
version: 1,
|
|
12
|
+
captureOutput: true,
|
|
13
|
+
maxOutputBytes: 20000,
|
|
14
|
+
enabledProviders: ['openai']
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// 3. Remote Config Fetch (Non-blocking, fire-and-forget)
|
|
18
|
+
setTimeout(async () => {
|
|
19
|
+
try {
|
|
20
|
+
const resp = await originalFetch('http://localhost:4000/config');
|
|
21
|
+
const remoteConfig = await resp.json();
|
|
22
|
+
if (remoteConfig && typeof remoteConfig === 'object') {
|
|
23
|
+
config = Object.assign({}, config, remoteConfig);
|
|
24
|
+
}
|
|
25
|
+
} catch (e) {
|
|
26
|
+
// Fail silently, use defaults
|
|
27
|
+
}
|
|
28
|
+
}, 0);
|
|
29
|
+
|
|
30
|
+
// 4. Internal telemetry sender (Neutral naming)
|
|
31
|
+
async function safeSendTelemetry(payload) {
|
|
32
|
+
try {
|
|
33
|
+
await originalFetch('http://localhost:4000/capture', {
|
|
34
|
+
method: 'POST',
|
|
35
|
+
headers: { 'Content-Type': 'application/json' },
|
|
36
|
+
body: JSON.stringify(payload)
|
|
37
|
+
});
|
|
38
|
+
} catch (e) {
|
|
39
|
+
// Fail silently
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 5. The Monkey Patch
|
|
44
|
+
global.fetch = async function (input, init) {
|
|
45
|
+
const start = Date.now();
|
|
46
|
+
const response = await originalFetch(input, init);
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// 6. Detection Logic (Dumb detection + Config Check)
|
|
50
|
+
const url = typeof input === 'string' ? input : (input && input.url);
|
|
51
|
+
const isAIProvider = config.enabledProviders.some(p => url && url.includes(`${p}.com/v1`));
|
|
52
|
+
|
|
53
|
+
if (url && isAIProvider) {
|
|
54
|
+
const latency = Date.now() - start;
|
|
55
|
+
const contentType = response.headers.get('content-type') || '';
|
|
56
|
+
const isStream = contentType.includes('text/event-stream');
|
|
57
|
+
|
|
58
|
+
// 7. Safe Body Capture (Best effort)
|
|
59
|
+
let prompt = undefined;
|
|
60
|
+
let model = undefined;
|
|
61
|
+
|
|
62
|
+
if (init && typeof init.body === 'string') {
|
|
63
|
+
prompt = init.body;
|
|
64
|
+
try {
|
|
65
|
+
const bodyJson = JSON.parse(prompt);
|
|
66
|
+
model = bodyJson.model;
|
|
67
|
+
} catch (e) { }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let output = undefined;
|
|
71
|
+
if (!isStream && config.captureOutput) {
|
|
72
|
+
try {
|
|
73
|
+
const cloned = response.clone();
|
|
74
|
+
output = await cloned.text();
|
|
75
|
+
// 8. Hard Capping (Infra Rule: Trust but verify)
|
|
76
|
+
if (output && output.length > config.maxOutputBytes) {
|
|
77
|
+
output = output.slice(0, config.maxOutputBytes);
|
|
78
|
+
}
|
|
79
|
+
} catch (e) { }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const payload = {
|
|
83
|
+
provider: 'openai',
|
|
84
|
+
url,
|
|
85
|
+
model,
|
|
86
|
+
prompt,
|
|
87
|
+
output,
|
|
88
|
+
latency_ms: latency,
|
|
89
|
+
timestamp: Math.floor(Date.now() / 1000)
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// 9. Fire-and-forget
|
|
93
|
+
setTimeout(() => safeSendTelemetry(payload), 0);
|
|
94
|
+
}
|
|
95
|
+
} catch (e) { }
|
|
96
|
+
|
|
97
|
+
return response;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
global.fetch.__dropout_patched__ = true;
|
|
101
|
+
})();
|