@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.
Files changed (3) hide show
  1. package/README.md +36 -0
  2. package/package.json +28 -0
  3. 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
+ })();