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