@dropout-ai/runtime 0.3.8 → 0.3.10

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 CHANGED
@@ -1,36 +1,186 @@
1
- # @dropout-ai/runtime
1
+ # 🕵️ Dropout AI Runtime (`@dropout-ai/runtime`)
2
2
 
3
- The invisible Node.js sensor for AI observability.
3
+ **Universal AI Behavior Analysis & Capture for Node.js**
4
4
 
5
- ## Overview
5
+ Dropout is a lightweight, "install-and-forget" runtime that automatically captures AI interactions (prompts & responses) from your application without requiring code changes. It acts as a passive observer, patching the network layer to ensure you never miss a conversation.
6
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.
7
+ * **🔌 Universal:** Works with OpenAI, Anthropic, Genkit, LangChain, Axios, and `fetch`.
8
+ * **🛡️ Safe:** Designed for production with silent failure modes and browser guards.
9
+ * **⚡ Zero-Latency:** Uses fire-and-forget logging to ensure your app stays fast.
10
+ * **🔋 Auto-Discovery:** Automatically detects framework (Next.js, NestJS) and configures itself.
8
11
 
9
- ## Installation
12
+ ---
13
+
14
+ ## 🚀 Quick Start (Recommended)
15
+
16
+ Run our initialization wizard in your project root. It will detect your framework and automatically create the necessary configuration files.
17
+
18
+ ```bash
19
+ npx @dropout-ai/runtime init
20
+
21
+ ```
22
+
23
+ *Supports: **Next.js** (App Router), **NestJS**, **Express**, and standard **Node.js** apps.*
24
+
25
+ ---
26
+
27
+ ## 🛠️ Manual Installation
28
+
29
+ If you prefer to configure it yourself, install the package and follow the guide for your specific framework.
10
30
 
11
31
  ```bash
12
32
  npm install @dropout-ai/runtime
33
+ # or
34
+ yarn add @dropout-ai/runtime
35
+
13
36
  ```
14
37
 
15
- ## Usage
38
+ ### 1. Next.js (App Router)
39
+
40
+ Next.js uses lazy-loading, so we use `instrumentation.ts` to ensure Dropout loads immediately on server boot.
41
+
42
+ **Create/Edit:** `src/instrumentation.ts` (or `instrumentation.ts` in root)
43
+
44
+ ```typescript
45
+ export async function register() {
46
+ // Ensure we only run on the Node.js server runtime
47
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
48
+ await import('@dropout-ai/runtime');
49
+ console.log('✅ Dropout AI Monitoring Active');
50
+ }
51
+ }
52
+
53
+ ```
16
54
 
17
- Simply import the package at the very top of your application's entry point (e.g., `index.js` or `instrumentation.ts`):
55
+ ### 2. Node.js / Express / Fastify
56
+
57
+ Initialize Dropout at the **very top** of your entry file (`index.js` or `server.js`), before importing other libraries.
18
58
 
19
59
  ```javascript
20
- import "@dropout-ai/runtime";
60
+ // MUST be the first line
61
+ require('@dropout-ai/runtime');
62
+
63
+ const express = require('express');
64
+ const app = express();
65
+ // ... rest of your code
66
+
21
67
  ```
22
68
 
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.
69
+ ### 3. NestJS
70
+
71
+ Initialize Dropout inside your `bootstrap()` function before creating the Nest app.
72
+
73
+ **File:** `src/main.ts`
74
+
75
+ ```typescript
76
+ import { NestFactory } from '@nestjs/core';
77
+ import { AppModule } from './app.module';
78
+ import Dropout from '@dropout-ai/runtime';
79
+
80
+ async function bootstrap() {
81
+ // ✅ Initialize first - Sends "Boot Ping" immediately
82
+ new Dropout({
83
+ projectId: process.env.DROPOUT_PROJECT_ID,
84
+ apiKey: process.env.DROPOUT_API_KEY
85
+ });
86
+
87
+ const app = await NestFactory.create(AppModule);
88
+ await app.listen(3000);
89
+ }
90
+ bootstrap();
91
+
92
+ ```
93
+
94
+ ### 4. Docker / Zero-Code Injection
95
+
96
+ You can inject Dropout into any Node.js application without editing a single file by using the Node `--require` flag. This is perfect for Docker containers or PaaS deployments.
97
+
98
+ ```bash
99
+ # 1. Set credentials
100
+ export DROPOUT_PROJECT_ID="your_project_id"
101
+ export DROPOUT_API_KEY="dp_live_..."
102
+
103
+ # 2. Run your app with the runtime injected
104
+ node -r @dropout-ai/runtime dist/index.js
105
+
106
+ ```
107
+
108
+ ---
109
+
110
+ ## ⚙️ Configuration
111
+
112
+ You can configure Dropout via **Environment Variables** (Recommended) or by passing options to the constructor.
113
+
114
+ | Environment Variable | Config Option | Description | Required |
115
+ | --- | --- | --- | --- |
116
+ | `DROPOUT_PROJECT_ID` | `projectId` | Your Project ID from dashboard. | ✅ |
117
+ | `DROPOUT_API_KEY` | `apiKey` | Your Secret Key (`dp_live_...`). | ✅ |
118
+ | `DROPOUT_DEBUG` | `debug` | Set `true` to see logs in console and enable connectivity checks. | No |
119
+ | `DROPOUT_PRIVACY` | `privacy` | `full` (capture text) or `mask` (metadata only). | No |
120
+
121
+ **Example `.env` file:**
122
+
123
+ ```bash
124
+ DROPOUT_PROJECT_ID="HomeOS"
125
+ DROPOUT_API_KEY="dp_live_xyz123..."
126
+ DROPOUT_DEBUG="true"
127
+
128
+ ```
129
+
130
+ ---
131
+
132
+ ## 🌐 Supported Environments
133
+
134
+ | Language/Framework | Status | Integration Method |
135
+ | --- | --- | --- |
136
+ | **Node.js** (v18+) | ✅ Ready | `require('@dropout-ai/runtime')` |
137
+ | **Next.js** | ✅ Ready | `instrumentation.ts` |
138
+ | **NestJS** | ✅ Ready | `main.ts` import |
139
+ | **Python / Java / Go** | 🚧 Beta | Use REST API (see below) |
140
+
141
+ ### Non-Node.js Usage (REST API)
142
+
143
+ If you are using Python, Java, or Go, you can manually send interaction logs to our ingestion endpoint.
144
+
145
+ **Endpoint:** `POST https://hipughmjlwmwjxzyxfzs.supabase.co/functions/v1/capture-sealed`
146
+
147
+ **Headers:**
148
+
149
+ * `Content-Type: application/json`
150
+ * `x-dropout-key: YOUR_API_KEY`
151
+
152
+ **Payload:**
153
+
154
+ ```json
155
+ {
156
+ "project_id": "your_project_id",
157
+ "turn_role": "assistant", // or "user"
158
+ "content": "AI response text...",
159
+ "model": "gpt-4", // optional
160
+ "provider": "openai", // optional
161
+ "latency_ms": 1250 // optional
162
+ }
163
+
164
+ ```
165
+
166
+ ---
167
+
168
+ ## ❓ Troubleshooting
169
+
170
+ **Q: I don't see any logs in the dashboard.**
171
+
172
+ 1. Ensure `DROPOUT_DEBUG="true"` is set.
173
+ 2. Check your console for `[Dropout] 🟢 Online: <ProjectID>`.
174
+ 3. If using Next.js, ensure you created `instrumentation.ts` in the correct folder (root or `src`).
175
+
176
+ **Q: Does this work with Edge Functions (Vercel Edge / Cloudflare)?**
177
+ No. This runtime relies on Node.js core modules (`http`, `https`) to capture traffic. It does not currently support Vercel Edge Runtime or Cloudflare Workers. Please ensure your API routes use the Node.js runtime.
178
+
179
+ **Q: Will this crash my app if the API is down?**
180
+ No. The SDK is built with a "Safety Fuse." If it cannot connect or encounters an error, it fails silently and your application continues running without interruption.
181
+
182
+ ---
28
183
 
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.
184
+ ### License
34
185
 
35
- ## License
36
- MIT
186
+ MIT
package/bin/init.js ADDED
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ const colors = {
6
+ reset: "\x1b[0m",
7
+ green: "\x1b[32m",
8
+ yellow: "\x1b[33m",
9
+ cyan: "\x1b[36m"
10
+ };
11
+
12
+ const log = (color, msg) => console.log(`${color}${msg}${colors.reset}`);
13
+
14
+ function detectFramework() {
15
+ const cwd = process.cwd();
16
+ if (fs.existsSync(path.resolve(cwd, 'package.json'))) {
17
+ const pkg = JSON.parse(fs.readFileSync(path.resolve(cwd, 'package.json'), 'utf-8'));
18
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
19
+ if (deps.next) return 'nextjs';
20
+ if (deps['@nestjs/core']) return 'nestjs';
21
+ return 'node';
22
+ }
23
+ return 'unknown';
24
+ }
25
+
26
+ function setupNextJs() {
27
+ log(colors.cyan, '🔍 Next.js project detected.');
28
+ const hasSrc = fs.existsSync(path.resolve(process.cwd(), 'src'));
29
+ const targetDir = hasSrc ? 'src' : '.';
30
+ const isTs = fs.existsSync(path.resolve(process.cwd(), 'tsconfig.json'));
31
+ const ext = isTs ? 'ts' : 'js';
32
+ const filePath = path.resolve(process.cwd(), targetDir, `instrumentation.${ext}`);
33
+
34
+ const content = `export async function register() {
35
+ if (process.env.NEXT_RUNTIME === 'nodejs') {
36
+ await import('@dropout-ai/runtime');
37
+ console.log('[Dropout] 🟢 Initialized via instrumentation hook');
38
+ }
39
+ }`;
40
+
41
+ if (fs.existsSync(filePath)) {
42
+ log(colors.yellow, `⚠️ File exists: ${filePath}`);
43
+ log(colors.yellow, `👉 Please add the register() hook manually.`);
44
+ } else {
45
+ fs.writeFileSync(filePath, content);
46
+ log(colors.green, `✅ Created: ${targetDir}/instrumentation.${ext}`);
47
+ }
48
+
49
+ log(colors.cyan, '\nNEXT STEP: Add .env keys (DROPOUT_PROJECT_ID, DROPOUT_API_KEY).');
50
+ }
51
+
52
+ function run() {
53
+ console.log(colors.cyan + "\nDropout AI - Init Wizard 🧙‍♂️" + colors.reset);
54
+ const framework = detectFramework();
55
+
56
+ switch (framework) {
57
+ case 'nextjs': setupNextJs(); break;
58
+ case 'nestjs':
59
+ log(colors.cyan, '🔍 NestJS detected.');
60
+ log(colors.green, '👉 Initialize in src/main.ts before bootstrap().');
61
+ break;
62
+ case 'node':
63
+ log(colors.cyan, '🔍 Node/Express detected.');
64
+ log(colors.green, '👉 Add require("@dropout-ai/runtime") at the top of index.js.');
65
+ break;
66
+ default:
67
+ log(colors.yellow, '⚠️ Unknown framework. Check docs for manual setup.');
68
+ }
69
+ }
70
+
71
+ run();
package/package.json CHANGED
@@ -1,16 +1,29 @@
1
1
  {
2
2
  "name": "@dropout-ai/runtime",
3
- "version": "0.3.8",
4
- "description": "Invisible Node.js runtime for capturing AI interactions.",
3
+ "version": "0.3.10",
4
+ "description": "Invisible Node.js runtime for understanding behavoiral impact of AI interactions.",
5
5
  "main": "src/index.js",
6
+ "types": "src/index.d.ts",
7
+ "bin": {
8
+ "init": "./bin/init.js"
9
+ },
6
10
  "scripts": {
7
- "test": "node test.js"
11
+ "test": "echo \"Error: no test specified\" && exit 1"
8
12
  },
9
13
  "keywords": [
10
14
  "openai",
11
15
  "telemetry",
12
16
  "runtime",
13
- "invisible"
17
+ "invisible",
18
+ "observability",
19
+ "ai",
20
+ "behavoiral analysis",
21
+ "ai forensic",
22
+ "llm",
23
+ "monitoring",
24
+ "genkit",
25
+ "nestjs",
26
+ "nextjs"
14
27
  ],
15
28
  "repository": {
16
29
  "type": "git",
@@ -18,7 +31,8 @@
18
31
  "directory": "packages/runtime"
19
32
  },
20
33
  "files": [
21
- "src"
34
+ "src",
35
+ "bin"
22
36
  ],
23
37
  "publishConfig": {
24
38
  "access": "public"
package/src/index.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ export interface DropoutConfig {
2
+ /**
3
+ * Your project ID from the Dropout Dashboard.
4
+ */
5
+ projectId: string;
6
+
7
+ /**
8
+ * Your secret API Key (starts with dp_live_...).
9
+ */
10
+ apiKey: string;
11
+
12
+ /**
13
+ * Data Privacy Level.
14
+ * 'full' - Captures prompts and responses (Default).
15
+ * 'mask' - Captures metadata only, redacts text.
16
+ */
17
+ privacy?: 'full' | 'mask';
18
+
19
+ /**
20
+ * Enable verbose logging for connection debugging.
21
+ * Default: false
22
+ */
23
+ debug?: boolean;
24
+ }
25
+
26
+ export default class Dropout {
27
+ constructor(config: DropoutConfig);
28
+
29
+ /**
30
+ * Manually log a message to the debug stream.
31
+ */
32
+ log(msg: string, ...args: any[]): void;
33
+ }
package/src/index.js CHANGED
@@ -19,13 +19,12 @@ const KNOWN_AI_DOMAINS = [
19
19
  class Dropout {
20
20
  constructor(config = {}) {
21
21
  // 🛡️ 1. BROWSER GUARD (Client-Side Protection)
22
- // If this accidentally runs in a browser/Client Component, we abort silently.
23
22
  if (typeof window !== 'undefined' && typeof window.document !== 'undefined') {
24
23
  if (config.debug) console.warn("[Dropout] ⚠️ Skipped: Browser detected. SDK is Server-Only.");
25
24
  return;
26
25
  }
27
26
 
28
- // 🛡️ 2. CREDENTIAL CHECK
27
+ // 🛡️ 2. CREDENTIAL GUARD
29
28
  if (!config.apiKey || !config.projectId) {
30
29
  if (config.debug) console.warn("[Dropout] ⚠️ Skipped: Missing API Key or Project ID.");
31
30
  return;
@@ -69,18 +68,18 @@ class Dropout {
69
68
  global.__dropout_session_id__ = this.generateSessionId();
70
69
  }
71
70
 
71
+ if (this.debug) console.log(`[Dropout] 🟢 Online: ${this.projectId}`);
72
+
72
73
  // Bindings
73
74
  this.log = this.log.bind(this);
74
75
  this.emit = this.emit.bind(this);
75
76
 
76
- // 🛡️ 4. SAFE STARTUP
77
+ // 4. Start Network Interceptor
77
78
  this.patchNetwork();
78
79
 
79
- // 🔍 5. CONNECTION VERIFICATION (Async Ping)
80
- // Only runs if debug is TRUE to confirm setup
81
- if (this.debug) {
82
- this.validateConnection();
83
- }
80
+ // 5. SEND STARTUP PING (Always run this, silent by default)
81
+ // This turns the dashboard status GREEN immediately on app boot.
82
+ this.sendStartupSignal();
84
83
 
85
84
  } catch (err) {
86
85
  // THE ULTIMATE CATCH-ALL: Ensure app NEVER crashes
@@ -101,9 +100,22 @@ class Dropout {
101
100
  }
102
101
  }
103
102
 
104
- // --- HEALTH CHECK ---
105
- validateConnection() {
106
- this.log("🔄 Verifying connection to Dropout Cloud...");
103
+ // --- HEARTBEAT ---
104
+ sendStartupSignal() {
105
+ const payload = JSON.stringify({
106
+ project_id: this.projectId,
107
+ session_id: 'system_boot_' + Date.now(),
108
+ turn_role: 'system',
109
+ turn_index: 0,
110
+ content: 'Dropout SDK Initialized',
111
+ // content_blob: 'Dropout SDK Initialized',
112
+ metadata_flags: {
113
+ type: 'system_boot',
114
+ environment: process.env.NODE_ENV || 'development',
115
+ runtime: 'node'
116
+ },
117
+ received_at: new Date().toISOString()
118
+ });
107
119
 
108
120
  const req = https.request(this.captureEndpoint, {
109
121
  method: 'POST',
@@ -111,26 +123,10 @@ class Dropout {
111
123
  'Content-Type': 'application/json',
112
124
  'x-dropout-key': this.apiKey
113
125
  }
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
126
  });
121
127
 
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
- }));
128
+ req.on('error', (e) => this.log("Startup Signal Failed", e.message));
129
+ req.write(payload);
134
130
  req.end();
135
131
  }
136
132
 
@@ -160,7 +156,7 @@ class Dropout {
160
156
  if (parsed.model) model = parsed.model;
161
157
  if (provider === 'custom' && (parsed.messages || parsed.prompt)) provider = 'heuristic';
162
158
  }
163
- } catch (e) { /* Ignore parsing errors */ }
159
+ } catch (e) { }
164
160
  return { provider, model };
165
161
  }
166
162
 
@@ -189,7 +185,7 @@ class Dropout {
189
185
  model: payload.model,
190
186
  latency_ms: payload.latency_ms || null,
191
187
  content: payload.content,
192
- //content_blob: payload.content,
188
+ // content_blob: payload.content,
193
189
  content_hash: payload.content_hash,
194
190
  metadata_flags: payload.metadata_flags,
195
191
  received_at: new Date().toISOString()