@mynitorai/sdk 0.1.6 → 0.1.8
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/dist/index.d.ts +4 -1
- package/dist/index.js +124 -15
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export interface MyNitorConfig {
|
|
6
6
|
apiKey: string;
|
|
7
|
+
environment?: string;
|
|
7
8
|
endpoint?: string;
|
|
8
9
|
}
|
|
9
10
|
export declare class MyNitor {
|
|
@@ -15,7 +16,7 @@ export declare class MyNitor {
|
|
|
15
16
|
private setupAutoFlush;
|
|
16
17
|
static init(config: MyNitorConfig): MyNitor;
|
|
17
18
|
/**
|
|
18
|
-
* Automatically detect and wrap AI libraries
|
|
19
|
+
* Automatically detect and wrap AI libraries: OpenAI, Anthropic, and Google Gemini
|
|
19
20
|
*/
|
|
20
21
|
instrument(): void;
|
|
21
22
|
/**
|
|
@@ -27,5 +28,7 @@ export declare class MyNitor {
|
|
|
27
28
|
private getCallSite;
|
|
28
29
|
private sendEvent;
|
|
29
30
|
private wrapOpenAI;
|
|
31
|
+
private wrapAnthropic;
|
|
32
|
+
private wrapGemini;
|
|
30
33
|
}
|
|
31
34
|
export declare const init: typeof MyNitor.init;
|
package/dist/index.js
CHANGED
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.init = exports.MyNitor = void 0;
|
|
8
|
+
// Symbol to prevent double-patching (Idempotency)
|
|
9
|
+
const WRAPPED_MARKER = Symbol('mynitor_wrapped');
|
|
8
10
|
class MyNitor {
|
|
9
11
|
constructor(config) {
|
|
10
12
|
this.isInstrumented = false;
|
|
11
13
|
this.pendingPromises = new Set();
|
|
12
14
|
this.config = {
|
|
15
|
+
environment: 'production',
|
|
13
16
|
endpoint: 'https://app.mynitor.ai/api/v1/events',
|
|
14
17
|
...config
|
|
15
18
|
};
|
|
@@ -21,7 +24,6 @@ class MyNitor {
|
|
|
21
24
|
process.env.NETLIFY ||
|
|
22
25
|
process.env.FUNCTIONS_WORKER_RUNTIME);
|
|
23
26
|
if (!isServerless && typeof process !== 'undefined' && typeof process.on === 'function') {
|
|
24
|
-
// Local script or long-running process
|
|
25
27
|
process.on('beforeExit', async () => {
|
|
26
28
|
await this.flush();
|
|
27
29
|
});
|
|
@@ -37,14 +39,16 @@ class MyNitor {
|
|
|
37
39
|
return MyNitor.instance;
|
|
38
40
|
}
|
|
39
41
|
/**
|
|
40
|
-
* Automatically detect and wrap AI libraries
|
|
42
|
+
* Automatically detect and wrap AI libraries: OpenAI, Anthropic, and Google Gemini
|
|
41
43
|
*/
|
|
42
44
|
instrument() {
|
|
43
45
|
if (this.isInstrumented)
|
|
44
46
|
return;
|
|
45
47
|
this.wrapOpenAI();
|
|
48
|
+
this.wrapAnthropic();
|
|
49
|
+
this.wrapGemini();
|
|
46
50
|
this.isInstrumented = true;
|
|
47
|
-
console.log('🚀 MyNitor: Auto-instrumentation active.');
|
|
51
|
+
console.log('🚀 MyNitor: Universal Auto-instrumentation active.');
|
|
48
52
|
}
|
|
49
53
|
/**
|
|
50
54
|
* Waits for all pending network requests to complete.
|
|
@@ -63,12 +67,8 @@ class MyNitor {
|
|
|
63
67
|
try {
|
|
64
68
|
const err = new Error();
|
|
65
69
|
const stack = err.stack?.split('\n') || [];
|
|
66
|
-
// Look for the frame that called the LLM method
|
|
67
|
-
// Stack usually: Error -> getCallSite -> wrapOpenAI wrapper -> USER CODE
|
|
68
|
-
// We iterate to find the first frame NOT in MyNitor SDK
|
|
69
70
|
for (const line of stack) {
|
|
70
71
|
if (!line.includes('mynitor') && !line.includes('Error') && line.includes('/')) {
|
|
71
|
-
// Typical format: " at Object.myFunction (/path/to/file.ts:10:5)"
|
|
72
72
|
const match = line.match(/at\s+(?:(.+?)\s+\()?(.*?):(\d+):(\d+)\)?/);
|
|
73
73
|
if (match) {
|
|
74
74
|
const func = match[1] || 'anonymous';
|
|
@@ -84,15 +84,11 @@ class MyNitor {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
|
-
catch (e) {
|
|
88
|
-
// fail safe
|
|
89
|
-
}
|
|
87
|
+
catch (e) { }
|
|
90
88
|
return { file: 'unknown', line: 0, functionName: 'unknown', workflowGuess: 'default-workflow' };
|
|
91
89
|
}
|
|
92
90
|
async sendEvent(payload) {
|
|
93
91
|
try {
|
|
94
|
-
// Fire and forget
|
|
95
|
-
// Fire and forget (but track)
|
|
96
92
|
const promise = fetch(this.config.endpoint, {
|
|
97
93
|
method: 'POST',
|
|
98
94
|
headers: {
|
|
@@ -101,6 +97,7 @@ class MyNitor {
|
|
|
101
97
|
},
|
|
102
98
|
body: JSON.stringify({
|
|
103
99
|
...payload,
|
|
100
|
+
environment: this.config.environment,
|
|
104
101
|
event_version: '1.0',
|
|
105
102
|
timestamp: new Date().toISOString()
|
|
106
103
|
})
|
|
@@ -119,14 +116,17 @@ class MyNitor {
|
|
|
119
116
|
const OpenAI = require('openai');
|
|
120
117
|
if (!OpenAI || !OpenAI.OpenAI)
|
|
121
118
|
return;
|
|
119
|
+
const target = OpenAI.OpenAI.Chat.Completions.prototype;
|
|
120
|
+
if (target[WRAPPED_MARKER])
|
|
121
|
+
return;
|
|
122
122
|
const self = this;
|
|
123
|
-
const
|
|
124
|
-
|
|
123
|
+
const originalCreate = target.create;
|
|
124
|
+
target.create = async function (...args) {
|
|
125
125
|
const start = Date.now();
|
|
126
126
|
const body = args[0];
|
|
127
127
|
const callsite = self.getCallSite();
|
|
128
128
|
try {
|
|
129
|
-
const result = await
|
|
129
|
+
const result = await originalCreate.apply(this, args);
|
|
130
130
|
const end = Date.now();
|
|
131
131
|
self.sendEvent({
|
|
132
132
|
request_id: result.id || `req_${Date.now()}`,
|
|
@@ -161,6 +161,115 @@ class MyNitor {
|
|
|
161
161
|
throw error;
|
|
162
162
|
}
|
|
163
163
|
};
|
|
164
|
+
target[WRAPPED_MARKER] = true;
|
|
165
|
+
}
|
|
166
|
+
catch (e) { }
|
|
167
|
+
}
|
|
168
|
+
wrapAnthropic() {
|
|
169
|
+
try {
|
|
170
|
+
const Anthropic = require('@anthropic-ai/sdk');
|
|
171
|
+
if (!Anthropic || !Anthropic.Messages)
|
|
172
|
+
return;
|
|
173
|
+
const target = Anthropic.Messages.prototype;
|
|
174
|
+
if (target[WRAPPED_MARKER])
|
|
175
|
+
return;
|
|
176
|
+
const self = this;
|
|
177
|
+
const originalCreate = target.create;
|
|
178
|
+
target.create = async function (...args) {
|
|
179
|
+
const start = Date.now();
|
|
180
|
+
const body = args[0];
|
|
181
|
+
const callsite = self.getCallSite();
|
|
182
|
+
try {
|
|
183
|
+
const result = await originalCreate.apply(this, args);
|
|
184
|
+
const end = Date.now();
|
|
185
|
+
self.sendEvent({
|
|
186
|
+
request_id: result.id || `ant_${Date.now()}`,
|
|
187
|
+
model: result.model || body.model,
|
|
188
|
+
provider: 'anthropic',
|
|
189
|
+
agent: 'default-agent',
|
|
190
|
+
workflow: callsite.workflowGuess,
|
|
191
|
+
file: callsite.file,
|
|
192
|
+
function_name: callsite.functionName,
|
|
193
|
+
line_number: callsite.line,
|
|
194
|
+
input_tokens: result.usage?.input_tokens || 0,
|
|
195
|
+
output_tokens: result.usage?.output_tokens || 0,
|
|
196
|
+
latency_ms: end - start,
|
|
197
|
+
status: 'success'
|
|
198
|
+
});
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
const end = Date.now();
|
|
203
|
+
self.sendEvent({
|
|
204
|
+
request_id: `err_ant_${Date.now()}`,
|
|
205
|
+
model: body?.model || 'unknown',
|
|
206
|
+
provider: 'anthropic',
|
|
207
|
+
agent: 'default-agent',
|
|
208
|
+
workflow: callsite.workflowGuess,
|
|
209
|
+
file: callsite.file,
|
|
210
|
+
function_name: callsite.functionName,
|
|
211
|
+
latency_ms: end - start,
|
|
212
|
+
status: 'error',
|
|
213
|
+
error_type: error?.constructor?.name || 'Error'
|
|
214
|
+
});
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
target[WRAPPED_MARKER] = true;
|
|
219
|
+
}
|
|
220
|
+
catch (e) { }
|
|
221
|
+
}
|
|
222
|
+
wrapGemini() {
|
|
223
|
+
try {
|
|
224
|
+
const GoogleGenAI = require('@google/generative-ai');
|
|
225
|
+
if (!GoogleGenAI || !GoogleGenAI.GenerativeModel)
|
|
226
|
+
return;
|
|
227
|
+
const target = GoogleGenAI.GenerativeModel.prototype;
|
|
228
|
+
if (target[WRAPPED_MARKER])
|
|
229
|
+
return;
|
|
230
|
+
const self = this;
|
|
231
|
+
const originalGenerate = target.generateContent;
|
|
232
|
+
target.generateContent = async function (...args) {
|
|
233
|
+
const start = Date.now();
|
|
234
|
+
const callsite = self.getCallSite();
|
|
235
|
+
try {
|
|
236
|
+
const result = await originalGenerate.apply(this, args);
|
|
237
|
+
const end = Date.now();
|
|
238
|
+
const metadata = result.response?.usageMetadata;
|
|
239
|
+
self.sendEvent({
|
|
240
|
+
request_id: `gem_${Date.now()}`,
|
|
241
|
+
model: this.model || 'gemini',
|
|
242
|
+
provider: 'google',
|
|
243
|
+
agent: 'default-agent',
|
|
244
|
+
workflow: callsite.workflowGuess,
|
|
245
|
+
file: callsite.file,
|
|
246
|
+
function_name: callsite.functionName,
|
|
247
|
+
line_number: callsite.line,
|
|
248
|
+
input_tokens: metadata?.promptTokenCount || 0,
|
|
249
|
+
output_tokens: metadata?.candidatesTokenCount || 0,
|
|
250
|
+
latency_ms: end - start,
|
|
251
|
+
status: 'success'
|
|
252
|
+
});
|
|
253
|
+
return result;
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
const end = Date.now();
|
|
257
|
+
self.sendEvent({
|
|
258
|
+
request_id: `err_gem_${Date.now()}`,
|
|
259
|
+
model: this.model || 'gemini',
|
|
260
|
+
provider: 'google',
|
|
261
|
+
agent: 'default-agent',
|
|
262
|
+
workflow: callsite.workflowGuess,
|
|
263
|
+
file: callsite.file,
|
|
264
|
+
function_name: callsite.functionName,
|
|
265
|
+
latency_ms: end - start,
|
|
266
|
+
status: 'error',
|
|
267
|
+
error_type: error?.constructor?.name || 'Error'
|
|
268
|
+
});
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
target[WRAPPED_MARKER] = true;
|
|
164
273
|
}
|
|
165
274
|
catch (e) { }
|
|
166
275
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mynitorai/sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"description": "Production safety and observability for AI systems.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -30,4 +30,4 @@
|
|
|
30
30
|
"openai": "^4.0.0",
|
|
31
31
|
"@types/node": "^20.0.0"
|
|
32
32
|
}
|
|
33
|
-
}
|
|
33
|
+
}
|