@rasputin-ai/node 0.2.0 → 0.3.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 (48) hide show
  1. package/README.md +128 -15
  2. package/dist/auto-instrumentation/bun-plugin.d.ts +23 -0
  3. package/dist/auto-instrumentation/bun-plugin.d.ts.map +1 -0
  4. package/dist/auto-instrumentation/collect-instrumentation-delta.d.ts +58 -0
  5. package/dist/auto-instrumentation/collect-instrumentation-delta.d.ts.map +1 -0
  6. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts +11 -0
  7. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts.map +1 -0
  8. package/dist/auto-instrumentation/node-hooks.d.ts +6 -0
  9. package/dist/auto-instrumentation/node-hooks.d.ts.map +1 -0
  10. package/dist/auto-instrumentation/schedule-instrumentation-manifest-upload.d.ts +16 -0
  11. package/dist/auto-instrumentation/schedule-instrumentation-manifest-upload.d.ts.map +1 -0
  12. package/dist/auto-instrumentation/sdk-source-boundary.d.ts +7 -0
  13. package/dist/auto-instrumentation/sdk-source-boundary.d.ts.map +1 -0
  14. package/dist/auto-instrumentation/source-classification.d.ts +10 -0
  15. package/dist/auto-instrumentation/source-classification.d.ts.map +1 -0
  16. package/dist/auto-instrumentation/source-map-locations.d.ts +3 -0
  17. package/dist/auto-instrumentation/source-map-locations.d.ts.map +1 -0
  18. package/dist/auto-instrumentation/transform-source.d.ts +18 -0
  19. package/dist/auto-instrumentation/transform-source.d.ts.map +1 -0
  20. package/dist/execution-recorder/automatic-runtime.d.ts +18 -0
  21. package/dist/execution-recorder/automatic-runtime.d.ts.map +1 -0
  22. package/dist/execution-recorder/call-aware-event-buffer.d.ts +23 -0
  23. package/dist/execution-recorder/call-aware-event-buffer.d.ts.map +1 -0
  24. package/dist/execution-recorder/execution-recorder-types.d.ts +71 -0
  25. package/dist/execution-recorder/execution-recorder-types.d.ts.map +1 -0
  26. package/dist/execution-recorder/execution-recorder.d.ts +4 -0
  27. package/dist/execution-recorder/execution-recorder.d.ts.map +1 -0
  28. package/dist/execution-recorder/index.d.ts +3 -0
  29. package/dist/execution-recorder/index.d.ts.map +1 -0
  30. package/dist/execution-recorder/recorder-memory-budget.d.ts +37 -0
  31. package/dist/execution-recorder/recorder-memory-budget.d.ts.map +1 -0
  32. package/dist/execution-recorder/safe-serialize.d.ts +10 -0
  33. package/dist/execution-recorder/safe-serialize.d.ts.map +1 -0
  34. package/dist/execution-recorder/source-exclusions.d.ts +9 -0
  35. package/dist/execution-recorder/source-exclusions.d.ts.map +1 -0
  36. package/dist/index.d.ts +5 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1198 -6
  39. package/dist/instrument/bun.d.ts +2 -0
  40. package/dist/instrument/bun.d.ts.map +1 -0
  41. package/dist/instrument/bun.js +477 -0
  42. package/dist/instrument/node.d.ts +2 -0
  43. package/dist/instrument/node.d.ts.map +1 -0
  44. package/dist/instrument/node.js +532 -0
  45. package/dist/rasputin-init.d.ts +22 -3
  46. package/dist/rasputin-init.d.ts.map +1 -1
  47. package/dist/sdk-meta.d.ts +1 -1
  48. package/package.json +15 -4
package/dist/index.js CHANGED
@@ -1,3 +1,1131 @@
1
+ // src/auto-instrumentation/schedule-instrumentation-manifest-upload.ts
2
+ import {
3
+ detectRelease,
4
+ resolveEndpointUrls,
5
+ uploadInstrumentationManifest
6
+ } from "@rasputin-ai/core";
7
+
8
+ // src/auto-instrumentation/instrumentation-manifest-registry.ts
9
+ import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve2, sep as sep2 } from "node:path";
10
+
11
+ // src/execution-recorder/automatic-runtime.ts
12
+ import { isAbsolute, relative, resolve, sep } from "node:path";
13
+ var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v1";
14
+ var AUTOMATIC_SOURCE_SEPARATOR = "\0";
15
+ var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
16
+ var normalizeAutomaticFunctionId = (sourceToken, repoRoot) => {
17
+ if (!sourceToken.includes(AUTOMATIC_SOURCE_SEPARATOR)) return sourceToken;
18
+ const [filePath, name, line, column] = sourceToken.split(AUTOMATIC_SOURCE_SEPARATOR);
19
+ if (!filePath || !name || !line || !column) return void 0;
20
+ const fromRoot = relative(repoRoot, resolve(filePath));
21
+ if (!fromRoot || fromRoot === ".." || fromRoot.startsWith(`..${sep}`) || isAbsolute(fromRoot)) {
22
+ return void 0;
23
+ }
24
+ return `${fromRoot.replaceAll("\\", "/")}:${name}@${line}:${column}`;
25
+ };
26
+ var getRegistry = () => {
27
+ const existing = Reflect.get(globalThis, runtimeSymbol);
28
+ if (existing?.__rasputinRuntimeRegistry && Array.isArray(existing.runtimes)) {
29
+ return existing;
30
+ }
31
+ const runtimes = [];
32
+ const registry = {
33
+ __rasputinRuntimeRegistry: true,
34
+ runtimes,
35
+ run: (functionId, args, callback) => {
36
+ const runtime = runtimes.at(-1);
37
+ return runtime ? runtime.run(functionId, args, callback) : callback();
38
+ }
39
+ };
40
+ Reflect.set(globalThis, runtimeSymbol, registry);
41
+ return registry;
42
+ };
43
+ var installAutomaticExecutionRuntime = (execution, options = {}) => {
44
+ const registry = getRegistry();
45
+ const repoRoot = options.repoRoot ? resolve(options.repoRoot) : void 0;
46
+ const normalizedIds = /* @__PURE__ */ new Map();
47
+ const runtime = {
48
+ run: (sourceToken, args, callback) => {
49
+ if (!sourceToken.includes(AUTOMATIC_SOURCE_SEPARATOR)) {
50
+ return execution.runFunction(sourceToken, args, callback);
51
+ }
52
+ if (!repoRoot) return callback();
53
+ let functionId = normalizedIds.get(sourceToken);
54
+ if (!normalizedIds.has(sourceToken)) {
55
+ functionId = normalizeAutomaticFunctionId(sourceToken, repoRoot);
56
+ normalizedIds.set(sourceToken, functionId);
57
+ }
58
+ return functionId ? execution.runFunction(functionId, args, callback) : callback();
59
+ }
60
+ };
61
+ registry.runtimes.push(runtime);
62
+ return () => {
63
+ const index = registry.runtimes.lastIndexOf(runtime);
64
+ if (index >= 0) registry.runtimes.splice(index, 1);
65
+ };
66
+ };
67
+
68
+ // src/auto-instrumentation/instrumentation-manifest-registry.ts
69
+ var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.v1";
70
+ var registrySymbol = Symbol.for(INSTRUMENTATION_MANIFEST_SYMBOL);
71
+ var getRegistry2 = () => {
72
+ const existing = Reflect.get(globalThis, registrySymbol);
73
+ if (existing?.__rasputinManifestRegistry && existing.files instanceof Map && typeof existing.generation === "number" && typeof existing.uploadedGeneration === "number") {
74
+ return existing;
75
+ }
76
+ const registry = {
77
+ __rasputinManifestRegistry: true,
78
+ files: /* @__PURE__ */ new Map(),
79
+ generation: 0,
80
+ uploadedGeneration: 0
81
+ };
82
+ Reflect.set(globalThis, registrySymbol, registry);
83
+ return registry;
84
+ };
85
+ var repoRelativePath = (filePath, repoRoot) => {
86
+ const fromRoot = relative2(repoRoot, resolve2(filePath));
87
+ if (!fromRoot || fromRoot === ".." || fromRoot.startsWith(`..${sep2}`) || isAbsolute2(fromRoot)) {
88
+ return void 0;
89
+ }
90
+ return fromRoot.replaceAll("\\", "/");
91
+ };
92
+ var isInstrumentationManifestDirty = () => {
93
+ const registry = getRegistry2();
94
+ return registry.generation !== registry.uploadedGeneration && registry.files.size > 0;
95
+ };
96
+ var instrumentationManifestGeneration = () => getRegistry2().generation;
97
+ var snapshotInstrumentationManifest = (repoRoot) => {
98
+ const functions = [];
99
+ const callSites = [];
100
+ const destructures = [];
101
+ const root = resolve2(repoRoot);
102
+ for (const delta of getRegistry2().files.values()) {
103
+ for (const fn of delta.functions) {
104
+ const functionId = normalizeAutomaticFunctionId(fn.sourceToken, root);
105
+ const path = repoRelativePath(fn.filePath, root);
106
+ if (!functionId || !path) continue;
107
+ functions.push({
108
+ functionId,
109
+ path,
110
+ name: fn.name,
111
+ startLine: fn.startLine,
112
+ startColumn: fn.startColumn,
113
+ endLine: fn.endLine,
114
+ endColumn: fn.endColumn,
115
+ params: fn.params
116
+ });
117
+ }
118
+ for (const site of delta.callSites) {
119
+ const callerFunctionId = normalizeAutomaticFunctionId(site.callerSourceToken, root);
120
+ const path = repoRelativePath(site.filePath, root);
121
+ if (!callerFunctionId || !path) continue;
122
+ callSites.push({
123
+ id: `${path}:${site.line}:${site.column}`,
124
+ callerFunctionId,
125
+ calleeName: site.calleeName,
126
+ calleeText: site.calleeText,
127
+ path,
128
+ line: site.line,
129
+ column: site.column,
130
+ endLine: site.endLine,
131
+ endColumn: site.endColumn,
132
+ resultBinding: site.resultBinding,
133
+ argIdentifiers: site.argIdentifiers
134
+ });
135
+ }
136
+ for (const destructure of delta.destructures) {
137
+ const callerFunctionId = normalizeAutomaticFunctionId(destructure.callerSourceToken, root);
138
+ const path = repoRelativePath(destructure.filePath, root);
139
+ if (!callerFunctionId || !path) continue;
140
+ destructures.push({
141
+ callerFunctionId,
142
+ sourceParam: destructure.sourceParam,
143
+ path,
144
+ line: destructure.line,
145
+ column: destructure.column,
146
+ binding: destructure.binding
147
+ });
148
+ }
149
+ }
150
+ if (functions.length === 0 && callSites.length === 0 && destructures.length === 0) {
151
+ return void 0;
152
+ }
153
+ return {
154
+ schema_version: 1,
155
+ functions,
156
+ callSites,
157
+ destructures
158
+ };
159
+ };
160
+ var markInstrumentationManifestUploaded = (generation) => {
161
+ const registry = getRegistry2();
162
+ if (registry.generation === generation) {
163
+ registry.uploadedGeneration = generation;
164
+ }
165
+ };
166
+ var setInstrumentationManifestOnDirty = (onDirty) => {
167
+ getRegistry2().onDirty = onDirty;
168
+ };
169
+
170
+ // src/auto-instrumentation/schedule-instrumentation-manifest-upload.ts
171
+ var noopHandle = {
172
+ flushSoon() {
173
+ },
174
+ async wait() {
175
+ },
176
+ disconnect() {
177
+ }
178
+ };
179
+ var scheduleInstrumentationManifestUpload = (options) => {
180
+ const release = detectRelease({ release: options.release });
181
+ const repoRoot = options.repoRoot;
182
+ if (!options.enabled || !release || !repoRoot || !options.projectApiKey.trim()) {
183
+ return noopHandle;
184
+ }
185
+ const { instrumentationManifestUrl } = resolveEndpointUrls(options.apiUrl);
186
+ let inflight;
187
+ const flushSoon = () => {
188
+ if (!isInstrumentationManifestDirty()) return;
189
+ const generation = instrumentationManifestGeneration();
190
+ const manifest = snapshotInstrumentationManifest(repoRoot);
191
+ if (!manifest) {
192
+ markInstrumentationManifestUploaded(generation);
193
+ return;
194
+ }
195
+ const work = uploadInstrumentationManifest({
196
+ url: instrumentationManifestUrl,
197
+ projectApiKey: options.projectApiKey,
198
+ release,
199
+ manifest,
200
+ fetch: options.fetch
201
+ }).then((result) => {
202
+ if (result === "ok") markInstrumentationManifestUploaded(generation);
203
+ });
204
+ inflight = inflight ? inflight.then(() => work) : work;
205
+ };
206
+ setInstrumentationManifestOnDirty(flushSoon);
207
+ flushSoon();
208
+ return {
209
+ flushSoon,
210
+ wait: async () => {
211
+ await inflight;
212
+ },
213
+ disconnect: () => {
214
+ setInstrumentationManifestOnDirty(void 0);
215
+ }
216
+ };
217
+ };
218
+
219
+ // src/execution-recorder/execution-recorder.ts
220
+ import { AsyncLocalStorage } from "node:async_hooks";
221
+
222
+ // src/execution-recorder/call-aware-event-buffer.ts
223
+ var CallAwareEventBuffer = class {
224
+ constructor(capacity) {
225
+ this.capacity = capacity;
226
+ }
227
+ items = [];
228
+ openCallIds = /* @__PURE__ */ new Set();
229
+ dropped = 0;
230
+ canStartCall() {
231
+ return this.items.length + this.openCallIds.size + 2 <= this.capacity;
232
+ }
233
+ startCall(event) {
234
+ this.items.push(event);
235
+ this.openCallIds.add(event.callId);
236
+ }
237
+ finishCall(event) {
238
+ if (event.type !== "function_exit" && event.type !== "function_throw") return;
239
+ if (!this.openCallIds.delete(event.callId)) return;
240
+ this.items.push(event);
241
+ }
242
+ dropCall() {
243
+ this.dropped += 2;
244
+ }
245
+ append(event) {
246
+ if (this.items.length + this.openCallIds.size + 1 > this.capacity) {
247
+ this.dropped++;
248
+ return false;
249
+ }
250
+ this.items.push(event);
251
+ return true;
252
+ }
253
+ forceCompletedCall(enter, terminal) {
254
+ while (this.items.length + this.openCallIds.size + 2 > this.capacity) {
255
+ if (!this.evictCompletedLeafOrSummary()) {
256
+ this.dropCall();
257
+ return false;
258
+ }
259
+ }
260
+ this.items.push(enter, terminal);
261
+ return true;
262
+ }
263
+ values() {
264
+ return [...this.items];
265
+ }
266
+ contains(target) {
267
+ return this.items.includes(target);
268
+ }
269
+ evictCompletedLeafOrSummary() {
270
+ const summaryIndex = this.items.findIndex(
271
+ (event) => event.type === "function_calls_suppressed"
272
+ );
273
+ if (summaryIndex >= 0) {
274
+ this.items.splice(summaryIndex, 1);
275
+ this.dropped++;
276
+ return true;
277
+ }
278
+ const parentCallIds = /* @__PURE__ */ new Set();
279
+ for (const event of this.items) {
280
+ if ((event.type === "function_enter" || event.type === "function_calls_suppressed") && event.parentCallId !== void 0) {
281
+ parentCallIds.add(event.parentCallId);
282
+ }
283
+ }
284
+ for (let index = 0; index < this.items.length; index++) {
285
+ const event = this.items[index];
286
+ if (event?.type !== "function_enter" || this.openCallIds.has(event.callId) || parentCallIds.has(event.callId)) {
287
+ continue;
288
+ }
289
+ const terminalIndex = this.items.findIndex(
290
+ (candidate) => (candidate.type === "function_exit" || candidate.type === "function_throw") && candidate.callId === event.callId
291
+ );
292
+ if (terminalIndex < 0) continue;
293
+ this.items.splice(Math.max(index, terminalIndex), 1);
294
+ this.items.splice(Math.min(index, terminalIndex), 1);
295
+ this.dropped += 2;
296
+ return true;
297
+ }
298
+ return false;
299
+ }
300
+ };
301
+
302
+ // src/execution-recorder/recorder-memory-budget.ts
303
+ import { Buffer } from "node:buffer";
304
+ var EXECUTION_BASE_ESTIMATED_BYTES = 1024;
305
+ var EVENT_PAIR_BASE_ESTIMATED_BYTES = 512;
306
+ var SUMMARY_EVENT_BASE_ESTIMATED_BYTES = 256;
307
+ var VALUE_HEAP_ESTIMATE_MULTIPLIER = 2;
308
+ var MEMORY_BUDGET_VALUE_MARKER = {
309
+ __rasputin_type: "omitted",
310
+ __reason: "process_memory_budget"
311
+ };
312
+ var RecorderMemoryBudget = class {
313
+ constructor(maximumBytes, maximumSerializedValueBytes) {
314
+ this.maximumBytes = maximumBytes;
315
+ this.maximumSerializedValueBytes = maximumSerializedValueBytes;
316
+ }
317
+ activeEstimatedBytes = 0;
318
+ stats = {
319
+ activeEstimatedBytes: 0,
320
+ peakActiveEstimatedBytes: 0,
321
+ memoryPressureExecutions: 0,
322
+ memoryPressureDegradations: 0,
323
+ valuesDroppedByMemoryBudget: 0,
324
+ eventsDroppedByMemoryBudget: 0
325
+ };
326
+ startExecution() {
327
+ const allocation = {
328
+ mode: "full",
329
+ estimatedBytes: 0,
330
+ released: false,
331
+ underPressure: false,
332
+ valuesDropped: 0,
333
+ eventsDropped: 0
334
+ };
335
+ if (!this.reserve(allocation, EXECUTION_BASE_ESTIMATED_BYTES)) {
336
+ this.markPressure(allocation, "metadata-only");
337
+ }
338
+ return allocation;
339
+ }
340
+ captureValue(allocation, serialize) {
341
+ if (allocation.mode !== "full") {
342
+ this.dropValues(allocation, 1);
343
+ return MEMORY_BUDGET_VALUE_MARKER;
344
+ }
345
+ const maximumEstimate = this.maximumSerializedValueBytes * VALUE_HEAP_ESTIMATE_MULTIPLIER;
346
+ if (this.activeEstimatedBytes + maximumEstimate > this.maximumBytes) {
347
+ this.markPressure(allocation, "structure-only");
348
+ this.dropValues(allocation, 1);
349
+ return MEMORY_BUDGET_VALUE_MARKER;
350
+ }
351
+ const serialized = serialize();
352
+ if (!this.reserve(allocation, serialized.bytes * VALUE_HEAP_ESTIMATE_MULTIPLIER)) {
353
+ this.markPressure(allocation, "structure-only");
354
+ this.dropValues(allocation, 1);
355
+ return MEMORY_BUDGET_VALUE_MARKER;
356
+ }
357
+ return serialized.value;
358
+ }
359
+ reserveCall(allocation, functionId) {
360
+ if (allocation.mode === "metadata-only") {
361
+ this.dropEvents(allocation, 2);
362
+ return false;
363
+ }
364
+ const estimatedBytes = EVENT_PAIR_BASE_ESTIMATED_BYTES + Buffer.byteLength(functionId, "utf8") * 2;
365
+ if (this.reserve(allocation, estimatedBytes)) return true;
366
+ this.markPressure(allocation, "metadata-only");
367
+ this.dropEvents(allocation, 2);
368
+ return false;
369
+ }
370
+ reserveStandaloneEvent(allocation) {
371
+ if (allocation.mode === "metadata-only") {
372
+ this.dropEvents(allocation, 1);
373
+ return false;
374
+ }
375
+ if (this.reserve(allocation, SUMMARY_EVENT_BASE_ESTIMATED_BYTES)) return true;
376
+ this.markPressure(allocation, "metadata-only");
377
+ this.dropEvents(allocation, 1);
378
+ return false;
379
+ }
380
+ release(allocation) {
381
+ if (allocation.released) return;
382
+ allocation.released = true;
383
+ this.activeEstimatedBytes = Math.max(0, this.activeEstimatedBytes - allocation.estimatedBytes);
384
+ this.stats.activeEstimatedBytes = this.activeEstimatedBytes;
385
+ }
386
+ getStats() {
387
+ return { ...this.stats };
388
+ }
389
+ reserve(allocation, bytes) {
390
+ const boundedBytes = Math.max(0, Math.ceil(bytes));
391
+ if (this.activeEstimatedBytes + boundedBytes > this.maximumBytes) return false;
392
+ this.activeEstimatedBytes += boundedBytes;
393
+ allocation.estimatedBytes += boundedBytes;
394
+ this.stats.activeEstimatedBytes = this.activeEstimatedBytes;
395
+ this.stats.peakActiveEstimatedBytes = Math.max(
396
+ this.stats.peakActiveEstimatedBytes,
397
+ this.activeEstimatedBytes
398
+ );
399
+ return true;
400
+ }
401
+ markPressure(allocation, mode) {
402
+ if (!allocation.underPressure) {
403
+ allocation.underPressure = true;
404
+ this.stats.memoryPressureExecutions++;
405
+ }
406
+ if (allocation.mode === mode || allocation.mode === "metadata-only" && mode === "structure-only") {
407
+ return;
408
+ }
409
+ allocation.mode = mode;
410
+ this.stats.memoryPressureDegradations++;
411
+ }
412
+ dropValues(allocation, count) {
413
+ allocation.valuesDropped += count;
414
+ this.stats.valuesDroppedByMemoryBudget += count;
415
+ }
416
+ dropEvents(allocation, count) {
417
+ allocation.eventsDropped += count;
418
+ this.stats.eventsDroppedByMemoryBudget += count;
419
+ }
420
+ };
421
+
422
+ // src/execution-recorder/safe-serialize.ts
423
+ import { Buffer as Buffer2 } from "node:buffer";
424
+ var DEFAULT_REDACT_KEYS = /* @__PURE__ */ new Set([
425
+ "password",
426
+ "token",
427
+ "authorization",
428
+ "cookie",
429
+ "secret",
430
+ "apikey",
431
+ "creditcard"
432
+ ]);
433
+ var defaults = {
434
+ maxDepth: 3,
435
+ maxObjectKeys: 30,
436
+ maxArrayElements: 20,
437
+ maxStringLength: 500,
438
+ maxSerializedValueBytes: 8 * 1024
439
+ };
440
+ var truncation = (reason, extra = {}) => ({
441
+ __rasputin_truncated: true,
442
+ __reason: reason,
443
+ ...extra
444
+ });
445
+ var typeMarker = (type, value) => ({
446
+ __rasputin_type: type,
447
+ ...value === void 0 ? {} : { value }
448
+ });
449
+ var redact = (item, redactKeys, seen) => {
450
+ if (!item || typeof item !== "object") return item;
451
+ if (seen.has(item)) return item;
452
+ seen.add(item);
453
+ if (Array.isArray(item)) {
454
+ return item.map((entry) => redact(entry, redactKeys, seen));
455
+ }
456
+ for (const [key, child] of Object.entries(item)) {
457
+ item[key] = redactKeys.has(key.toLowerCase()) ? "[RASPUTIN_REDACTED]" : redact(child, redactKeys, seen);
458
+ }
459
+ return item;
460
+ };
461
+ var errorValue = (error, depth, seen, limits) => ({
462
+ __rasputin_type: "Error",
463
+ name: error.name,
464
+ message: serializeInner(error.message, depth + 1, seen, limits),
465
+ ...error.stack ? { stack: serializeInner(error.stack, depth + 1, seen, limits) } : {}
466
+ });
467
+ var serializeInner = (value, depth, seen, limits) => {
468
+ if (value === null || typeof value === "boolean" || typeof value === "number") return value;
469
+ if (typeof value === "string") {
470
+ if (value.length <= limits.maxStringLength) return value;
471
+ return truncation("max_string_length", {
472
+ __original_length: value.length,
473
+ value: value.slice(0, limits.maxStringLength)
474
+ });
475
+ }
476
+ if (typeof value === "undefined") return typeMarker("undefined");
477
+ if (typeof value === "bigint") return typeMarker("bigint", value.toString());
478
+ if (typeof value === "symbol") return typeMarker("symbol", String(value));
479
+ if (typeof value === "function") return typeMarker("function", value.name || "anonymous");
480
+ if (depth >= limits.maxDepth) return truncation("max_depth");
481
+ if (typeof value !== "object") return typeMarker(typeof value);
482
+ if (seen.has(value)) return typeMarker("circular");
483
+ seen.add(value);
484
+ try {
485
+ if (value instanceof Error) return errorValue(value, depth, seen, limits);
486
+ if (value instanceof Date) return typeMarker("Date", value.toISOString());
487
+ if (Buffer2.isBuffer(value)) {
488
+ return typeMarker("Buffer", {
489
+ byteLength: value.byteLength,
490
+ base64: value.subarray(0, limits.maxStringLength).toString("base64"),
491
+ ...value.byteLength > limits.maxStringLength ? { __rasputin_truncated: true } : {}
492
+ });
493
+ }
494
+ if (value instanceof Promise) return typeMarker("Promise");
495
+ if (value instanceof Map) {
496
+ const entries = [];
497
+ for (const entry of value.entries()) {
498
+ if (entries.length >= limits.maxArrayElements) break;
499
+ entries.push([
500
+ serializeInner(entry[0], depth + 1, seen, limits),
501
+ serializeInner(entry[1], depth + 1, seen, limits)
502
+ ]);
503
+ }
504
+ return {
505
+ __rasputin_type: "Map",
506
+ entries,
507
+ ...value.size > limits.maxArrayElements ? truncation("max_array_elements", { __original_length: value.size }) : {}
508
+ };
509
+ }
510
+ if (value instanceof Set) {
511
+ const items = [];
512
+ for (const item of value.values()) {
513
+ if (items.length >= limits.maxArrayElements) break;
514
+ items.push(serializeInner(item, depth + 1, seen, limits));
515
+ }
516
+ return {
517
+ __rasputin_type: "Set",
518
+ values: items,
519
+ ...value.size > limits.maxArrayElements ? truncation("max_array_elements", { __original_length: value.size }) : {}
520
+ };
521
+ }
522
+ if (Array.isArray(value)) {
523
+ const maxItems = value.length > limits.maxArrayElements ? limits.maxArrayElements - 1 : value.length;
524
+ const items = value.slice(0, maxItems).map((item) => serializeInner(item, depth + 1, seen, limits));
525
+ if (value.length > limits.maxArrayElements) {
526
+ items.push(truncation("max_array_elements", { __original_length: value.length }));
527
+ }
528
+ return items;
529
+ }
530
+ const prototype = Object.getPrototypeOf(value);
531
+ const serialized = {};
532
+ if (prototype && prototype !== Object.prototype) {
533
+ serialized.__rasputin_type = prototype.constructor?.name ?? "instance";
534
+ }
535
+ const keys = Object.keys(value);
536
+ const maxKeys = keys.length > limits.maxObjectKeys ? limits.maxObjectKeys - 1 : keys.length;
537
+ for (const key of keys.slice(0, maxKeys)) {
538
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
539
+ serialized[key] = descriptor && "value" in descriptor ? serializeInner(descriptor.value, depth + 1, seen, limits) : typeMarker("accessor");
540
+ }
541
+ if (keys.length > limits.maxObjectKeys) {
542
+ Object.assign(serialized, truncation("max_object_keys", { __original_length: keys.length }));
543
+ }
544
+ return serialized;
545
+ } catch (error) {
546
+ return typeMarker("unserializable", error instanceof Error ? error.message : void 0);
547
+ } finally {
548
+ seen.delete(value);
549
+ }
550
+ };
551
+ var safeSerializeWithBytes = (value, options = {}) => {
552
+ const limits = {
553
+ maxDepth: options.maxDepth ?? defaults.maxDepth,
554
+ maxObjectKeys: options.maxObjectKeys ?? defaults.maxObjectKeys,
555
+ maxArrayElements: options.maxArrayElements ?? defaults.maxArrayElements,
556
+ maxStringLength: options.maxStringLength ?? defaults.maxStringLength,
557
+ maxSerializedValueBytes: options.maxSerializedValueBytes ?? defaults.maxSerializedValueBytes
558
+ };
559
+ try {
560
+ const redactKeys = /* @__PURE__ */ new Set([
561
+ ...DEFAULT_REDACT_KEYS,
562
+ ...(options.redactKeys ?? []).map((key) => key.toLowerCase())
563
+ ]);
564
+ const serialized = serializeInner(value, 0, /* @__PURE__ */ new WeakSet(), limits);
565
+ const redacted = redact(serialized, redactKeys, /* @__PURE__ */ new WeakSet());
566
+ const bytes = Buffer2.byteLength(JSON.stringify(redacted), "utf8");
567
+ const bounded = bytes <= limits.maxSerializedValueBytes ? redacted : truncation("max_serialized_value_bytes", { __serialized_bytes: bytes });
568
+ return {
569
+ value: bounded,
570
+ bytes: bounded === redacted ? bytes : Buffer2.byteLength(JSON.stringify(bounded), "utf8")
571
+ };
572
+ } catch {
573
+ const value2 = typeMarker("unserializable");
574
+ return { value: value2, bytes: Buffer2.byteLength(JSON.stringify(value2), "utf8") };
575
+ }
576
+ };
577
+ var safeSerialize = (value, options = {}) => safeSerializeWithBytes(value, options).value;
578
+
579
+ // src/execution-recorder/source-exclusions.ts
580
+ var sourcePathFrom = (functionId) => {
581
+ const separator = functionId.indexOf(":");
582
+ return (separator < 0 ? functionId : functionId.slice(0, separator)).replaceAll("\\", "/");
583
+ };
584
+ var normalizePattern = (pattern) => pattern.trim().replaceAll("\\", "/").replace(/^\.\//, "").replace(/\/$/, "");
585
+ var globRegex = (pattern) => {
586
+ let expression = "";
587
+ for (let index = 0; index < pattern.length; index++) {
588
+ const character = pattern[index];
589
+ const next = pattern[index + 1];
590
+ if (character === "*" && next === "*") {
591
+ if (pattern[index + 2] === "/") {
592
+ expression += "(?:.*/)?";
593
+ index += 2;
594
+ } else {
595
+ expression += ".*";
596
+ index++;
597
+ }
598
+ continue;
599
+ }
600
+ if (character === "*") {
601
+ expression += "[^/]*";
602
+ continue;
603
+ }
604
+ if (character === "?") {
605
+ expression += "[^/]";
606
+ continue;
607
+ }
608
+ expression += character?.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") ?? "";
609
+ }
610
+ return new RegExp(`^${expression}$`);
611
+ };
612
+ var createSourceExclusionMatcher = (exclusions) => {
613
+ const matchers = (exclusions ?? []).flatMap((exclusion) => {
614
+ if (exclusion instanceof RegExp) {
615
+ return [
616
+ (functionId) => {
617
+ exclusion.lastIndex = 0;
618
+ const matched = exclusion.test(functionId);
619
+ exclusion.lastIndex = 0;
620
+ return matched;
621
+ }
622
+ ];
623
+ }
624
+ const pattern = normalizePattern(exclusion);
625
+ if (!pattern) return [];
626
+ if (pattern.includes("*") || pattern.includes("?")) {
627
+ const regex = globRegex(pattern);
628
+ return [(functionId) => regex.test(sourcePathFrom(functionId))];
629
+ }
630
+ return [
631
+ (functionId) => {
632
+ const sourcePath = sourcePathFrom(functionId);
633
+ return sourcePath === pattern || sourcePath.startsWith(`${pattern}/`);
634
+ }
635
+ ];
636
+ });
637
+ return matchers.length === 0 ? () => false : (functionId) => matchers.some((matcher) => matcher(functionId));
638
+ };
639
+
640
+ // src/execution-recorder/execution-recorder.ts
641
+ var DEFAULT_MAX_ACTIVE_MEMORY_BYTES = 64 * 1024 * 1024;
642
+ var elapsedMs = (startedAtNs, endedAtNs = process.hrtime.bigint()) => Number(endedAtNs - startedAtNs) / 1e6;
643
+ var roundedMilliseconds = (nanoseconds) => Math.round(Number(nanoseconds) / 1e6 * 1e4) / 1e4;
644
+ var addRecorderTime = (recording, nanoseconds) => {
645
+ if (nanoseconds > 0n) recording.inlineRecorderTimeNs += nanoseconds;
646
+ };
647
+ var isPromiseLike = (value) => Boolean(value) && typeof value.then === "function";
648
+ var notifyObserver = (observer, value) => {
649
+ try {
650
+ observer(value);
651
+ } catch {
652
+ }
653
+ };
654
+ var observeCallback = (callback, onReturn, onThrow) => {
655
+ let result;
656
+ try {
657
+ result = callback();
658
+ } catch (error) {
659
+ notifyObserver(onThrow, error);
660
+ throw error;
661
+ }
662
+ if (isPromiseLike(result)) {
663
+ return result.then(
664
+ (value) => {
665
+ notifyObserver(onReturn, value);
666
+ return value;
667
+ },
668
+ (error) => {
669
+ notifyObserver(onThrow, error);
670
+ throw error;
671
+ }
672
+ );
673
+ }
674
+ notifyObserver(onReturn, result);
675
+ return result;
676
+ };
677
+ var observeRecordedCallback = (recording, recorderStartedAtNs, callback, onReturn, onThrow) => {
678
+ const callbackStartedAtNs = process.hrtime.bigint();
679
+ const recordExitWork = (callbackEndedAtNs2) => {
680
+ addRecorderTime(recording, callbackStartedAtNs - recorderStartedAtNs);
681
+ addRecorderTime(recording, process.hrtime.bigint() - callbackEndedAtNs2);
682
+ };
683
+ let result;
684
+ try {
685
+ result = callback();
686
+ } catch (error) {
687
+ const callbackEndedAtNs2 = process.hrtime.bigint();
688
+ notifyObserver(onThrow, error);
689
+ recordExitWork(callbackEndedAtNs2);
690
+ throw error;
691
+ }
692
+ if (isPromiseLike(result)) {
693
+ return result.then(
694
+ (value) => {
695
+ const callbackEndedAtNs2 = process.hrtime.bigint();
696
+ notifyObserver(onReturn, value);
697
+ recordExitWork(callbackEndedAtNs2);
698
+ return value;
699
+ },
700
+ (error) => {
701
+ const callbackEndedAtNs2 = process.hrtime.bigint();
702
+ notifyObserver(onThrow, error);
703
+ recordExitWork(callbackEndedAtNs2);
704
+ throw error;
705
+ }
706
+ );
707
+ }
708
+ const callbackEndedAtNs = process.hrtime.bigint();
709
+ notifyObserver(onReturn, result);
710
+ recordExitWork(callbackEndedAtNs);
711
+ return result;
712
+ };
713
+ var noOpExecution = {
714
+ createScope: () => ({
715
+ run: (callback) => callback(),
716
+ getErrorState: () => void 0,
717
+ finish: () => {
718
+ }
719
+ }),
720
+ run: (_metadata, callback) => callback(),
721
+ getErrorState: () => void 0,
722
+ runFunction: (_functionId, _args, callback) => callback(),
723
+ trace: (_functionId, fn) => fn,
724
+ getStats: () => ({
725
+ errorSnapshots: 0,
726
+ successfulExecutionsDiscarded: 0,
727
+ truncatedEvents: 0,
728
+ repeatedCallsSuppressed: 0,
729
+ activeExecutions: 0,
730
+ peakActiveExecutions: 0,
731
+ activeEstimatedBytes: 0,
732
+ peakActiveEstimatedBytes: 0,
733
+ memoryPressureExecutions: 0,
734
+ memoryPressureDegradations: 0,
735
+ valuesDroppedByMemoryBudget: 0,
736
+ eventsDroppedByMemoryBudget: 0,
737
+ partialErrorSnapshots: 0
738
+ })
739
+ };
740
+ var mergeExecutionMetadata = (target, metadata) => {
741
+ if (metadata.kind) target.kind = metadata.kind;
742
+ if (metadata.name !== void 0) target.name = metadata.name;
743
+ if (metadata.request) target.request = { ...target.request, ...metadata.request };
744
+ };
745
+ var createExecutionRecorder = (options) => {
746
+ if (!options?.enabled) return noOpExecution;
747
+ const storage = new AsyncLocalStorage();
748
+ const maxEventsPerExecution = Math.max(2, Math.floor(options.maxEventsPerExecution ?? 500));
749
+ const maxCapturedCallsPerFunction = Math.max(
750
+ 1,
751
+ Math.floor(options.maxCapturedCallsPerFunction ?? 3)
752
+ );
753
+ const maxActiveMemoryBytes = Math.max(
754
+ 0,
755
+ Math.floor(options.maxActiveMemoryBytes ?? DEFAULT_MAX_ACTIVE_MEMORY_BYTES)
756
+ );
757
+ const memoryBudget = new RecorderMemoryBudget(
758
+ maxActiveMemoryBytes,
759
+ options.maxSerializedValueBytes ?? 8 * 1024
760
+ );
761
+ const excludesSource = createSourceExclusionMatcher(options.excludeSources);
762
+ let executionCounter = 0;
763
+ const errorExecutions = /* @__PURE__ */ new WeakMap();
764
+ const stats = {
765
+ errorSnapshots: 0,
766
+ successfulExecutionsDiscarded: 0,
767
+ truncatedEvents: 0,
768
+ repeatedCallsSuppressed: 0,
769
+ activeExecutions: 0,
770
+ peakActiveExecutions: 0,
771
+ partialErrorSnapshots: 0
772
+ };
773
+ const captureValue = (recording, value) => memoryBudget.captureValue(recording.memory, () => safeSerializeWithBytes(value, options));
774
+ const stateFrom = (recording, endedAtNs = process.hrtime.bigint()) => {
775
+ const recorderStartedAtNs = process.hrtime.bigint();
776
+ const state = {
777
+ version: 1,
778
+ executionId: recording.executionId,
779
+ startedAt: recording.startedAt,
780
+ durationMs: elapsedMs(recording.startedAtNs, endedAtNs),
781
+ recorder: { inlineWallTimeMs: 0 },
782
+ execution: {
783
+ ...recording.execution,
784
+ ...recording.execution.request ? { request: { ...recording.execution.request } } : {}
785
+ },
786
+ events: recording.events.values().map((event) => event.type === "function_calls_suppressed" ? { ...event } : event),
787
+ ...recording.events.dropped > 0 ? { truncated: { eventsDropped: recording.events.dropped } } : {},
788
+ ...recording.memory.underPressure ? {
789
+ capture: {
790
+ completeness: "partial",
791
+ reason: "process_memory_budget",
792
+ valuesDropped: recording.memory.valuesDropped,
793
+ eventsDropped: recording.memory.eventsDropped
794
+ }
795
+ } : {}
796
+ };
797
+ addRecorderTime(recording, process.hrtime.bigint() - recorderStartedAtNs);
798
+ state.recorder = { inlineWallTimeMs: roundedMilliseconds(recording.inlineRecorderTimeNs) };
799
+ return state;
800
+ };
801
+ const rememberError = (error, recording) => {
802
+ recording.hasError = true;
803
+ if (typeof error !== "object" && typeof error !== "function" || error === null) return;
804
+ if (!errorExecutions.has(error)) {
805
+ stats.errorSnapshots++;
806
+ if (recording.memory.underPressure && !recording.partialSnapshotCounted) {
807
+ recording.partialSnapshotCounted = true;
808
+ stats.partialErrorSnapshots++;
809
+ }
810
+ }
811
+ errorExecutions.set(error, recording);
812
+ };
813
+ const countDroppedEvents = (recording, droppedBefore) => {
814
+ stats.truncatedEvents += recording.events.dropped - droppedBefore;
815
+ };
816
+ const appendStandaloneEvent = (recording, event) => {
817
+ if (!memoryBudget.reserveStandaloneEvent(recording.memory)) return;
818
+ const droppedBefore = recording.events.dropped;
819
+ recording.events.append(event);
820
+ countDroppedEvents(recording, droppedBefore);
821
+ };
822
+ const repeatedCallGroup = (recording, functionId, parentCallId) => {
823
+ let byParent = recording.repeatedCalls.get(functionId);
824
+ if (!byParent) {
825
+ byParent = /* @__PURE__ */ new Map();
826
+ recording.repeatedCalls.set(functionId, byParent);
827
+ }
828
+ let group = byParent.get(parentCallId);
829
+ if (!group) {
830
+ group = { observedCalls: 0 };
831
+ byParent.set(parentCallId, group);
832
+ }
833
+ return group;
834
+ };
835
+ const serializedError = (recording, error, callId) => {
836
+ const key = (typeof error === "object" || typeof error === "function") && error !== null ? error : void 0;
837
+ const referencedCallId = key ? recording.capturedErrors.get(key) : void 0;
838
+ if (referencedCallId !== void 0) {
839
+ return {
840
+ value: { __rasputin_type: "ErrorReference", callId: referencedCallId },
841
+ commit: () => {
842
+ }
843
+ };
844
+ }
845
+ return {
846
+ // Keep the bounded thrown value even after surrounding capture degrades. A partial
847
+ // snapshot without the exception itself would not be useful for investigation.
848
+ value: safeSerialize(error, options),
849
+ commit: () => {
850
+ if (key) recording.capturedErrors.set(key, callId);
851
+ }
852
+ };
853
+ };
854
+ const captureCompletedThrow = (recording, functionId, args, parentCallId, startedAtNs, startedAtMs, error) => {
855
+ if (!memoryBudget.reserveCall(recording.memory, functionId)) {
856
+ rememberError(error, recording);
857
+ return;
858
+ }
859
+ const callId = ++recording.nextCallId;
860
+ const capturedError = serializedError(recording, error, callId);
861
+ const enter = {
862
+ type: "function_enter",
863
+ callId,
864
+ functionId,
865
+ timestampMs: startedAtMs,
866
+ args: Array.from(args, (argument) => captureValue(recording, argument)),
867
+ ...parentCallId === void 0 ? {} : { parentCallId }
868
+ };
869
+ const terminal = {
870
+ type: "function_throw",
871
+ callId,
872
+ timestampMs: elapsedMs(recording.startedAtNs),
873
+ durationMs: elapsedMs(startedAtNs),
874
+ error: capturedError.value
875
+ };
876
+ const droppedBefore = recording.events.dropped;
877
+ const retained = recording.events.forceCompletedCall(enter, terminal);
878
+ if (retained) capturedError.commit();
879
+ countDroppedEvents(recording, droppedBefore);
880
+ rememberError(error, recording);
881
+ };
882
+ const recordSuppressedCall = (recording, group, functionId, parentCallId, startedAtMs, durationMs) => {
883
+ stats.repeatedCallsSuppressed++;
884
+ const endedAtMs = startedAtMs + durationMs;
885
+ if (!group.summary) {
886
+ group.summary = {
887
+ type: "function_calls_suppressed",
888
+ functionId,
889
+ timestampMs: startedAtMs,
890
+ lastTimestampMs: endedAtMs,
891
+ suppressedCallCount: 1,
892
+ totalDurationMs: durationMs,
893
+ ...parentCallId === void 0 ? {} : { parentCallId }
894
+ };
895
+ appendStandaloneEvent(recording, group.summary);
896
+ return;
897
+ }
898
+ group.summary.lastTimestampMs = endedAtMs;
899
+ group.summary.suppressedCallCount++;
900
+ group.summary.totalDurationMs = Math.round((group.summary.totalDurationMs + durationMs) * 1e4) / 1e4;
901
+ if (!recording.events.contains(group.summary)) appendStandaloneEvent(recording, group.summary);
902
+ };
903
+ const finish = (store, metadata) => {
904
+ const { recording } = store;
905
+ if (recording.finished) return;
906
+ const recorderStartedAtNs = process.hrtime.bigint();
907
+ recording.finished = true;
908
+ if (metadata) mergeExecutionMetadata(recording.execution, metadata);
909
+ memoryBudget.release(recording.memory);
910
+ stats.activeExecutions = Math.max(0, stats.activeExecutions - 1);
911
+ if (!recording.hasError) stats.successfulExecutionsDiscarded++;
912
+ recording.finishedAtNs = process.hrtime.bigint();
913
+ addRecorderTime(recording, recording.finishedAtNs - recorderStartedAtNs);
914
+ };
915
+ const createScope = (metadata) => {
916
+ const recorderStartedAtNs = process.hrtime.bigint();
917
+ try {
918
+ const recording = {
919
+ executionId: `${Date.now()}-${process.pid}-${++executionCounter}`,
920
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
921
+ startedAtNs: recorderStartedAtNs,
922
+ inlineRecorderTimeNs: 0n,
923
+ execution: {
924
+ ...metadata,
925
+ ...metadata.request ? { request: { ...metadata.request } } : {}
926
+ },
927
+ events: new CallAwareEventBuffer(maxEventsPerExecution),
928
+ repeatedCalls: /* @__PURE__ */ new Map(),
929
+ capturedErrors: /* @__PURE__ */ new WeakMap(),
930
+ nextCallId: 0,
931
+ finished: false,
932
+ hasError: false,
933
+ memory: memoryBudget.startExecution(),
934
+ partialSnapshotCounted: false
935
+ };
936
+ stats.activeExecutions++;
937
+ stats.peakActiveExecutions = Math.max(stats.peakActiveExecutions, stats.activeExecutions);
938
+ addRecorderTime(recording, process.hrtime.bigint() - recorderStartedAtNs);
939
+ const store = { recording };
940
+ return {
941
+ run: (callback) => {
942
+ const runStartedAtNs = process.hrtime.bigint();
943
+ return storage.run(store, () => {
944
+ addRecorderTime(recording, process.hrtime.bigint() - runStartedAtNs);
945
+ return callback();
946
+ });
947
+ },
948
+ getErrorState: (error, errorMetadata) => {
949
+ const getStateStartedAtNs = process.hrtime.bigint();
950
+ if (errorMetadata) mergeExecutionMetadata(recording.execution, errorMetadata);
951
+ rememberError(error, recording);
952
+ addRecorderTime(recording, process.hrtime.bigint() - getStateStartedAtNs);
953
+ return stateFrom(recording, recording.finishedAtNs);
954
+ },
955
+ finish: (finishMetadata) => finish(store, finishMetadata)
956
+ };
957
+ } catch {
958
+ return noOpExecution.createScope(metadata);
959
+ }
960
+ };
961
+ const run = (metadata, callback) => {
962
+ const scope = createScope(metadata);
963
+ return scope.run(
964
+ () => observeCallback(
965
+ callback,
966
+ () => scope.finish(),
967
+ (error) => {
968
+ try {
969
+ const store = storage.getStore();
970
+ if (store) {
971
+ const recorderStartedAtNs = process.hrtime.bigint();
972
+ rememberError(error, store.recording);
973
+ addRecorderTime(store.recording, process.hrtime.bigint() - recorderStartedAtNs);
974
+ }
975
+ } finally {
976
+ scope.finish();
977
+ }
978
+ }
979
+ )
980
+ );
981
+ };
982
+ const getErrorState = (error, metadata) => {
983
+ const getStateStartedAtNs = process.hrtime.bigint();
984
+ try {
985
+ const weakKey = (typeof error === "object" || typeof error === "function") && error !== null ? error : void 0;
986
+ const recording = (weakKey ? errorExecutions.get(weakKey) : void 0) ?? storage.getStore()?.recording;
987
+ if (!recording) return void 0;
988
+ if (metadata) mergeExecutionMetadata(recording.execution, metadata);
989
+ rememberError(error, recording);
990
+ addRecorderTime(recording, process.hrtime.bigint() - getStateStartedAtNs);
991
+ return stateFrom(recording, recording.finishedAtNs);
992
+ } catch {
993
+ return void 0;
994
+ }
995
+ };
996
+ const runFunction = (functionId, args, callback) => {
997
+ const store = storage.getStore();
998
+ if (!store || store.recording.finished) return callback();
999
+ const recorderStartedAtNs = process.hrtime.bigint();
1000
+ if (store.suppressCapture || excludesSource(functionId)) {
1001
+ return storage.run({ ...store, suppressCapture: true }, () => {
1002
+ addRecorderTime(store.recording, process.hrtime.bigint() - recorderStartedAtNs);
1003
+ return callback();
1004
+ });
1005
+ }
1006
+ const parentCallId = store.currentCallId;
1007
+ const group = repeatedCallGroup(store.recording, functionId, parentCallId);
1008
+ group.observedCalls++;
1009
+ if (group.observedCalls > maxCapturedCallsPerFunction) {
1010
+ const startedAtNs2 = process.hrtime.bigint();
1011
+ const startedAtMs = elapsedMs(store.recording.startedAtNs, startedAtNs2);
1012
+ const captureSuccess = () => recordSuppressedCall(
1013
+ store.recording,
1014
+ group,
1015
+ functionId,
1016
+ parentCallId,
1017
+ startedAtMs,
1018
+ elapsedMs(startedAtNs2)
1019
+ );
1020
+ return observeRecordedCallback(
1021
+ store.recording,
1022
+ recorderStartedAtNs,
1023
+ callback,
1024
+ captureSuccess,
1025
+ (error) => captureCompletedThrow(
1026
+ store.recording,
1027
+ functionId,
1028
+ args,
1029
+ parentCallId,
1030
+ startedAtNs2,
1031
+ startedAtMs,
1032
+ error
1033
+ )
1034
+ );
1035
+ }
1036
+ const startedAtNs = process.hrtime.bigint();
1037
+ if (!store.recording.events.canStartCall()) {
1038
+ const startedAtMs = elapsedMs(store.recording.startedAtNs, startedAtNs);
1039
+ const captureSuccess = () => {
1040
+ const droppedBefore = store.recording.events.dropped;
1041
+ store.recording.events.dropCall();
1042
+ countDroppedEvents(store.recording, droppedBefore);
1043
+ };
1044
+ return storage.run(
1045
+ store,
1046
+ () => observeRecordedCallback(
1047
+ store.recording,
1048
+ recorderStartedAtNs,
1049
+ callback,
1050
+ captureSuccess,
1051
+ (error) => captureCompletedThrow(
1052
+ store.recording,
1053
+ functionId,
1054
+ args,
1055
+ parentCallId,
1056
+ startedAtNs,
1057
+ startedAtMs,
1058
+ error
1059
+ )
1060
+ )
1061
+ );
1062
+ }
1063
+ if (!memoryBudget.reserveCall(store.recording.memory, functionId)) {
1064
+ return observeRecordedCallback(
1065
+ store.recording,
1066
+ recorderStartedAtNs,
1067
+ callback,
1068
+ () => {
1069
+ },
1070
+ (error) => rememberError(error, store.recording)
1071
+ );
1072
+ }
1073
+ const callId = ++store.recording.nextCallId;
1074
+ const enter = {
1075
+ type: "function_enter",
1076
+ callId,
1077
+ functionId,
1078
+ timestampMs: elapsedMs(store.recording.startedAtNs),
1079
+ args: Array.from(args, (argument) => captureValue(store.recording, argument)),
1080
+ ...parentCallId === void 0 ? {} : { parentCallId }
1081
+ };
1082
+ store.recording.events.startCall(enter);
1083
+ const callStore = { recording: store.recording, currentCallId: callId };
1084
+ return storage.run(
1085
+ callStore,
1086
+ () => observeRecordedCallback(
1087
+ store.recording,
1088
+ recorderStartedAtNs,
1089
+ callback,
1090
+ (value) => {
1091
+ store.recording.events.finishCall({
1092
+ type: "function_exit",
1093
+ callId,
1094
+ timestampMs: elapsedMs(store.recording.startedAtNs),
1095
+ durationMs: elapsedMs(startedAtNs),
1096
+ returnValue: captureValue(store.recording, value)
1097
+ });
1098
+ },
1099
+ (error) => {
1100
+ const capturedError = serializedError(store.recording, error, callId);
1101
+ store.recording.events.finishCall({
1102
+ type: "function_throw",
1103
+ callId,
1104
+ timestampMs: elapsedMs(store.recording.startedAtNs),
1105
+ durationMs: elapsedMs(startedAtNs),
1106
+ error: capturedError.value
1107
+ });
1108
+ capturedError.commit();
1109
+ rememberError(error, store.recording);
1110
+ }
1111
+ )
1112
+ );
1113
+ };
1114
+ function trace(functionId, fn) {
1115
+ return function traced(...args) {
1116
+ return runFunction(functionId, args, () => fn.apply(this, args));
1117
+ };
1118
+ }
1119
+ return {
1120
+ createScope,
1121
+ run,
1122
+ getErrorState,
1123
+ runFunction,
1124
+ trace,
1125
+ getStats: () => ({ ...stats, ...memoryBudget.getStats() })
1126
+ };
1127
+ };
1128
+
1
1129
  // src/install-global-handlers.ts
2
1130
  var removeProcessListener = process.off.bind(process);
3
1131
  var installGlobalHandlers = (client, options = {}) => {
@@ -49,26 +1177,90 @@ var installGlobalHandlers = (client, options = {}) => {
49
1177
  // src/rasputin-init.ts
50
1178
  import {
51
1179
  createClient,
52
- isClientEnabled
1180
+ isClientEnabled,
1181
+ resolveRepoRoot
53
1182
  } from "@rasputin-ai/core";
54
1183
 
55
1184
  // src/sdk-meta.ts
56
1185
  var SDK_NAME = "@rasputin-ai/node";
57
- var SDK_VERSION = "0.2.0";
1186
+ var SDK_VERSION = "0.3.0";
58
1187
 
59
1188
  // src/rasputin-init.ts
1189
+ var withExecution = (client, execution, installRuntime, repoRoot, manifest) => {
1190
+ const uninstallRuntime = installRuntime ? installAutomaticExecutionRuntime(execution, { repoRoot }) : () => {
1191
+ };
1192
+ const manifestUpload = scheduleInstrumentationManifestUpload({
1193
+ ...manifest,
1194
+ repoRoot
1195
+ });
1196
+ return {
1197
+ ...client,
1198
+ execution,
1199
+ getStats: () => ({ ...client.getStats(), recorder: execution.getStats() }),
1200
+ captureException: (error, context) => {
1201
+ const runtimeState = context?.runtimeState ?? execution.getErrorState(
1202
+ error,
1203
+ context?.request ? { kind: "http", request: context.request } : void 0
1204
+ );
1205
+ const result = client.captureException(error, { ...context, runtimeState });
1206
+ manifestUpload.flushSoon();
1207
+ return result;
1208
+ },
1209
+ flush: async (timeoutMs) => {
1210
+ await client.flush(timeoutMs);
1211
+ await manifestUpload.wait();
1212
+ },
1213
+ close: async () => {
1214
+ uninstallRuntime();
1215
+ manifestUpload.disconnect();
1216
+ await client.close();
1217
+ await manifestUpload.wait();
1218
+ }
1219
+ };
1220
+ };
60
1221
  var RasputinInit = (options) => {
61
1222
  try {
62
1223
  const client = createClient(options, { sdkVersion: SDK_VERSION, sdkName: SDK_NAME });
1224
+ const recorderEnabled = isClientEnabled(options) && options.executionRecorder?.enabled !== false;
1225
+ const execution = createExecutionRecorder({
1226
+ ...options.executionRecorder,
1227
+ enabled: recorderEnabled
1228
+ });
1229
+ const wrappedClient = withExecution(
1230
+ client,
1231
+ execution,
1232
+ recorderEnabled,
1233
+ resolveRepoRoot(options),
1234
+ {
1235
+ projectApiKey: options.projectApiKey,
1236
+ release: options.release,
1237
+ apiUrl: options.apiUrl,
1238
+ enabled: isClientEnabled(options)
1239
+ }
1240
+ );
63
1241
  if (isClientEnabled(options)) {
64
- installGlobalHandlers(client);
1242
+ installGlobalHandlers(wrappedClient);
65
1243
  }
66
- return client;
1244
+ return wrappedClient;
67
1245
  } catch {
68
- return createClient({ ...options, enabled: false });
1246
+ return withExecution(
1247
+ createClient({ ...options, enabled: false }),
1248
+ createExecutionRecorder({ enabled: false }),
1249
+ false,
1250
+ void 0,
1251
+ {
1252
+ projectApiKey: options.projectApiKey,
1253
+ release: options.release,
1254
+ apiUrl: options.apiUrl,
1255
+ enabled: false
1256
+ }
1257
+ );
69
1258
  }
70
1259
  };
71
1260
  export {
72
1261
  RasputinInit,
73
- installGlobalHandlers
1262
+ createExecutionRecorder,
1263
+ installAutomaticExecutionRuntime,
1264
+ installGlobalHandlers,
1265
+ scheduleInstrumentationManifestUpload
74
1266
  };