@rasputin-ai/node 0.4.1 → 0.5.0-alpha.2

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 (37) hide show
  1. package/README.md +188 -162
  2. package/dist/auto-instrumentation/collect-instrumentation-delta.d.ts +2 -37
  3. package/dist/auto-instrumentation/collect-instrumentation-delta.d.ts.map +1 -1
  4. package/dist/auto-instrumentation/instrument-compiled-esm.d.ts +17 -0
  5. package/dist/auto-instrumentation/instrument-compiled-esm.d.ts.map +1 -0
  6. package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts +40 -0
  7. package/dist/auto-instrumentation/instrumentation-manifest-delta.d.ts.map +1 -0
  8. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts +4 -1
  9. package/dist/auto-instrumentation/instrumentation-manifest-registry.d.ts.map +1 -1
  10. package/dist/auto-instrumentation/transform-source.d.ts +1 -1
  11. package/dist/auto-instrumentation/transform-source.d.ts.map +1 -1
  12. package/dist/auto-instrumentation/warn-production-runtime-transform.d.ts +8 -0
  13. package/dist/auto-instrumentation/warn-production-runtime-transform.d.ts.map +1 -0
  14. package/dist/execution-recorder/automatic-runtime.d.ts +19 -10
  15. package/dist/execution-recorder/automatic-runtime.d.ts.map +1 -1
  16. package/dist/execution-recorder/call-aware-event-buffer.d.ts +17 -3
  17. package/dist/execution-recorder/call-aware-event-buffer.d.ts.map +1 -1
  18. package/dist/execution-recorder/execution-recorder-types.d.ts +19 -0
  19. package/dist/execution-recorder/execution-recorder-types.d.ts.map +1 -1
  20. package/dist/execution-recorder/execution-recorder.d.ts.map +1 -1
  21. package/dist/execution-recorder/function-plan.d.ts +26 -0
  22. package/dist/execution-recorder/function-plan.d.ts.map +1 -0
  23. package/dist/execution-recorder/recorder-memory-budget.d.ts +7 -1
  24. package/dist/execution-recorder/recorder-memory-budget.d.ts.map +1 -1
  25. package/dist/execution-recorder/safe-serialize.d.ts +7 -0
  26. package/dist/execution-recorder/safe-serialize.d.ts.map +1 -1
  27. package/dist/index.js +546 -294
  28. package/dist/instrument/build.d.ts +2 -0
  29. package/dist/instrument/build.d.ts.map +1 -0
  30. package/dist/instrument/build.js +707 -0
  31. package/dist/instrument/bun.js +55 -21
  32. package/dist/instrument/node.js +55 -21
  33. package/dist/rasputin-init.d.ts +5 -5
  34. package/dist/rasputin-init.d.ts.map +1 -1
  35. package/dist/sdk-meta.d.ts +1 -1
  36. package/dist/sdk-meta.d.ts.map +1 -1
  37. package/package.json +10 -2
package/dist/index.js CHANGED
@@ -104,76 +104,38 @@ var runtimeLabel = () => {
104
104
 
105
105
  // src/auto-instrumentation/instrumentation-manifest-registry.ts
106
106
  import { createHash } from "node:crypto";
107
+ import { existsSync, readFileSync } from "node:fs";
107
108
  import {
108
109
  INSTRUMENTATION_MANIFEST_SCHEMA_VERSION,
109
110
  sourceLocatorFromFile as sourceLocatorFromFile2
110
111
  } from "@rasputin-ai/core";
111
112
 
112
- // src/execution-recorder/automatic-runtime.ts
113
+ // src/execution-recorder/function-plan.ts
113
114
  import {
114
115
  runtimeFunctionId,
115
116
  sourceLocatorFromFile
116
117
  } 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);
118
+ var EVENT_PAIR_BASE_ESTIMATED_BYTES = 512;
119
+ var definitionFromEncodedPlan = (plan) => {
120
+ const [functionId, name, packageName, packageRelativePath, line, column] = plan;
121
+ const source = packageRelativePath ? {
122
+ ...packageName ? { packageName } : {},
123
+ packageRelativePath,
124
+ ...line == null ? {} : { line },
125
+ ...column == null ? {} : { column }
126
+ } : void 0;
127
+ return {
128
+ functionId,
129
+ name,
130
+ ...source ? { source } : {}
170
131
  };
171
132
  };
133
+ var estimatedCallBytesFor = (functionId) => EVENT_PAIR_BASE_ESTIMATED_BYTES + Buffer.byteLength(functionId, "utf8") * 2;
172
134
 
173
135
  // src/auto-instrumentation/instrumentation-manifest-registry.ts
174
136
  var INSTRUMENTATION_MANIFEST_SYMBOL = "rasputin.instrumentation.manifest.v2";
175
137
  var registrySymbol = Symbol.for(INSTRUMENTATION_MANIFEST_SYMBOL);
176
- var getRegistry2 = () => {
138
+ var getRegistry = () => {
177
139
  const existing = Reflect.get(globalThis, registrySymbol);
178
140
  if (existing?.__rasputinManifestRegistry && existing.files instanceof Map && typeof existing.generation === "number" && typeof existing.uploadedGeneration === "number") {
179
141
  return existing;
@@ -192,20 +154,46 @@ var sourceAt = (filePath, line, column) => {
192
154
  return source ? { ...source, line, column } : void 0;
193
155
  };
194
156
  var stableId = (kind, ...parts) => `${kind}_${createHash("sha256").update(parts.join("\0")).digest("base64url").slice(0, 24)}`;
157
+ var recordPrebuiltInstrumentationManifest = (manifest) => {
158
+ const registry = getRegistry();
159
+ registry.prebuilt = manifest;
160
+ registry.generation += 1;
161
+ registry.onDirty?.();
162
+ };
163
+ var loadInstrumentationManifestFile = (path) => {
164
+ try {
165
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
166
+ if (parsed.schema_version !== INSTRUMENTATION_MANIFEST_SCHEMA_VERSION) return false;
167
+ if (!Array.isArray(parsed.functions)) return false;
168
+ recordPrebuiltInstrumentationManifest(parsed);
169
+ return true;
170
+ } catch {
171
+ return false;
172
+ }
173
+ };
174
+ var loadInstrumentationManifestIfPresent = (path) => {
175
+ if (!path || !existsSync(path)) return;
176
+ loadInstrumentationManifestFile(path);
177
+ };
195
178
  var isInstrumentationManifestDirty = () => {
196
- const registry = getRegistry2();
197
- return registry.generation !== registry.uploadedGeneration && registry.files.size > 0;
179
+ const registry = getRegistry();
180
+ return registry.generation !== registry.uploadedGeneration && (registry.files.size > 0 || registry.prebuilt !== void 0);
181
+ };
182
+ var instrumentationManifestGeneration = () => getRegistry().generation;
183
+ var instrumentationManifestModuleCount = () => {
184
+ const registry = getRegistry();
185
+ if (registry.files.size > 0) return registry.files.size;
186
+ if (!registry.prebuilt) return 0;
187
+ return new Set(registry.prebuilt.functions.map((fn) => fn.source.packageRelativePath)).size;
198
188
  };
199
- var instrumentationManifestGeneration = () => getRegistry2().generation;
200
- var instrumentationManifestModuleCount = () => getRegistry2().files.size;
201
189
  var snapshotInstrumentationManifest = (options = {}) => {
202
190
  const functions = [];
203
191
  const callSites = [];
204
192
  const destructures = [];
205
- for (const delta of getRegistry2().files.values()) {
193
+ for (const delta of getRegistry().files.values()) {
206
194
  for (const fn of delta.functions) {
207
- const definition = decodeAutomaticFunctionSource(fn.sourceToken);
208
- if (!definition?.source) continue;
195
+ const definition = definitionFromEncodedPlan(fn.plan);
196
+ if (!definition.source) continue;
209
197
  functions.push({
210
198
  functionId: definition.functionId,
211
199
  source: definition.source,
@@ -218,19 +206,18 @@ var snapshotInstrumentationManifest = (options = {}) => {
218
206
  });
219
207
  }
220
208
  for (const site of delta.callSites) {
221
- const caller = decodeAutomaticFunctionSource(site.callerSourceToken);
222
209
  const source = sourceAt(site.filePath, site.line, site.column);
223
- if (!caller || !source) continue;
210
+ if (!source) continue;
224
211
  callSites.push({
225
212
  id: stableId(
226
213
  "cs",
227
- caller.functionId,
214
+ site.callerFunctionId,
228
215
  source.packageName ?? "",
229
216
  source.packageRelativePath,
230
217
  site.line,
231
218
  site.column
232
219
  ),
233
- callerFunctionId: caller.functionId,
220
+ callerFunctionId: site.callerFunctionId,
234
221
  calleeName: site.calleeName,
235
222
  calleeText: site.calleeText,
236
223
  source,
@@ -243,11 +230,10 @@ var snapshotInstrumentationManifest = (options = {}) => {
243
230
  });
244
231
  }
245
232
  for (const destructure of delta.destructures) {
246
- const caller = decodeAutomaticFunctionSource(destructure.callerSourceToken);
247
233
  const source = sourceAt(destructure.filePath, destructure.line, destructure.column);
248
- if (!caller || !source) continue;
234
+ if (!source) continue;
249
235
  destructures.push({
250
- callerFunctionId: caller.functionId,
236
+ callerFunctionId: destructure.callerFunctionId,
251
237
  sourceParam: destructure.sourceParam,
252
238
  source,
253
239
  line: destructure.line,
@@ -256,6 +242,18 @@ var snapshotInstrumentationManifest = (options = {}) => {
256
242
  });
257
243
  }
258
244
  }
245
+ const registry = getRegistry();
246
+ if (registry.prebuilt && registry.files.size === 0) {
247
+ const prebuilt = registry.prebuilt;
248
+ if (!options.sourceRoot && !options.sourceRoots) return prebuilt;
249
+ return {
250
+ ...prebuilt,
251
+ sourceRoots: {
252
+ ...options.sourceRoot ? { default: options.sourceRoot } : {},
253
+ packages: { ...options.sourceRoots ?? {} }
254
+ }
255
+ };
256
+ }
259
257
  if (functions.length === 0 && callSites.length === 0 && destructures.length === 0) {
260
258
  return void 0;
261
259
  }
@@ -274,11 +272,11 @@ var snapshotInstrumentationManifest = (options = {}) => {
274
272
  };
275
273
  };
276
274
  var markInstrumentationManifestUploaded = (generation) => {
277
- const registry = getRegistry2();
275
+ const registry = getRegistry();
278
276
  if (registry.generation === generation) registry.uploadedGeneration = generation;
279
277
  };
280
278
  var setInstrumentationManifestOnDirty = (onDirty) => {
281
- getRegistry2().onDirty = onDirty;
279
+ getRegistry().onDirty = onDirty;
282
280
  };
283
281
 
284
282
  // src/auto-instrumentation/schedule-instrumentation-manifest-upload.ts
@@ -389,6 +387,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
389
387
  import { runtimeFunctionId as runtimeFunctionId2 } from "@rasputin-ai/core";
390
388
 
391
389
  // src/execution-recorder/call-aware-event-buffer.ts
390
+ var isSuppressionPlaceholder = (item) => "kind" in item && item.kind === "suppressed";
392
391
  var CallAwareEventBuffer = class {
393
392
  constructor(capacity) {
394
393
  this.capacity = capacity;
@@ -411,12 +410,14 @@ var CallAwareEventBuffer = class {
411
410
  dropCall() {
412
411
  this.dropped += 2;
413
412
  }
414
- append(event) {
413
+ appendSuppressed(group) {
415
414
  if (this.items.length + this.openCallIds.size + 1 > this.capacity) {
416
415
  this.dropped++;
417
416
  return false;
418
417
  }
419
- this.items.push(event);
418
+ const placeholder = { kind: "suppressed", group };
419
+ this.items.push(placeholder);
420
+ group.retainedSlot = placeholder;
420
421
  return true;
421
422
  }
422
423
  forceCompletedCall(enter, terminal) {
@@ -432,31 +433,27 @@ var CallAwareEventBuffer = class {
432
433
  values() {
433
434
  return [...this.items];
434
435
  }
435
- contains(target) {
436
- return this.items.includes(target);
437
- }
438
436
  evictCompletedLeafOrSummary() {
439
- const summaryIndex = this.items.findIndex(
440
- (event) => event.type === "function_calls_suppressed"
441
- );
437
+ const summaryIndex = this.items.findIndex((item) => isSuppressionPlaceholder(item));
442
438
  if (summaryIndex >= 0) {
439
+ const summary = this.items[summaryIndex];
440
+ if (summary && isSuppressionPlaceholder(summary)) summary.group.retainedSlot = void 0;
443
441
  this.items.splice(summaryIndex, 1);
444
442
  this.dropped++;
445
443
  return true;
446
444
  }
447
445
  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
- }
446
+ for (const item of this.items) {
447
+ const parentCallId = isSuppressionPlaceholder(item) ? item.group.parentCallId : item.type === "function_enter" ? item.parentCallId : void 0;
448
+ if (parentCallId !== void 0) parentCallIds.add(parentCallId);
452
449
  }
453
450
  for (let index = 0; index < this.items.length; index++) {
454
451
  const event = this.items[index];
455
- if (event?.type !== "function_enter" || this.openCallIds.has(event.callId) || parentCallIds.has(event.callId)) {
452
+ if (!event || isSuppressionPlaceholder(event) || event.type !== "function_enter" || this.openCallIds.has(event.callId) || parentCallIds.has(event.callId)) {
456
453
  continue;
457
454
  }
458
455
  const terminalIndex = this.items.findIndex(
459
- (candidate) => (candidate.type === "function_exit" || candidate.type === "function_throw") && candidate.callId === event.callId
456
+ (candidate) => !isSuppressionPlaceholder(candidate) && (candidate.type === "function_exit" || candidate.type === "function_throw") && candidate.callId === event.callId
460
457
  );
461
458
  if (terminalIndex < 0) continue;
462
459
  this.items.splice(Math.max(index, terminalIndex), 1);
@@ -469,9 +466,7 @@ var CallAwareEventBuffer = class {
469
466
  };
470
467
 
471
468
  // src/execution-recorder/recorder-memory-budget.ts
472
- import { Buffer } from "node:buffer";
473
469
  var EXECUTION_BASE_ESTIMATED_BYTES = 1024;
474
- var EVENT_PAIR_BASE_ESTIMATED_BYTES = 512;
475
470
  var SUMMARY_EVENT_BASE_ESTIMATED_BYTES = 256;
476
471
  var VALUE_HEAP_ESTIMATE_MULTIPLIER = 2;
477
472
  var MEMORY_BUDGET_VALUE_MARKER = {
@@ -525,12 +520,11 @@ var RecorderMemoryBudget = class {
525
520
  }
526
521
  return serialized.value;
527
522
  }
528
- reserveCall(allocation, functionId) {
523
+ reserveCall(allocation, estimatedBytes) {
529
524
  if (allocation.mode === "metadata-only") {
530
525
  this.dropEvents(allocation, 2);
531
526
  return false;
532
527
  }
533
- const estimatedBytes = EVENT_PAIR_BASE_ESTIMATED_BYTES + Buffer.byteLength(functionId, "utf8") * 2;
534
528
  if (this.reserve(allocation, estimatedBytes)) return true;
535
529
  this.markPressure(allocation, "metadata-only");
536
530
  this.dropEvents(allocation, 2);
@@ -590,7 +584,7 @@ var RecorderMemoryBudget = class {
590
584
 
591
585
  // src/execution-recorder/safe-serialize.ts
592
586
  import { Buffer as Buffer2 } from "node:buffer";
593
- var DEFAULT_REDACT_KEYS = /* @__PURE__ */ new Set([
587
+ var DEFAULT_REDACT_KEYS = [
594
588
  "password",
595
589
  "token",
596
590
  "authorization",
@@ -598,7 +592,7 @@ var DEFAULT_REDACT_KEYS = /* @__PURE__ */ new Set([
598
592
  "secret",
599
593
  "apikey",
600
594
  "creditcard"
601
- ]);
595
+ ];
602
596
  var defaults = {
603
597
  maxDepth: 3,
604
598
  maxObjectKeys: 30,
@@ -606,6 +600,10 @@ var defaults = {
606
600
  maxStringLength: 500,
607
601
  maxSerializedValueBytes: 8 * 1024
608
602
  };
603
+ var TYPE_MARKER_OPEN = '{"__rasputin_type":"';
604
+ var TYPE_MARKER_VALUE_MID = '","value":"';
605
+ var TYPE_MARKER_CLOSE = '"}';
606
+ var UNDEFINED_MARKER_BYTES = TYPE_MARKER_OPEN.length + "undefined".length + TYPE_MARKER_CLOSE.length;
609
607
  var truncation = (reason, extra = {}) => ({
610
608
  __rasputin_truncated: true,
611
609
  __reason: reason,
@@ -615,135 +613,224 @@ var typeMarker = (type, value) => ({
615
613
  __rasputin_type: type,
616
614
  ...value === void 0 ? {} : { value }
617
615
  });
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);
616
+ var REDACTED = "[RASPUTIN_REDACTED]";
617
+ var compileLimits = (options) => ({
618
+ maxDepth: options.maxDepth ?? defaults.maxDepth,
619
+ maxObjectKeys: options.maxObjectKeys ?? defaults.maxObjectKeys,
620
+ maxArrayElements: options.maxArrayElements ?? defaults.maxArrayElements,
621
+ maxStringLength: options.maxStringLength ?? defaults.maxStringLength,
622
+ maxSerializedValueBytes: options.maxSerializedValueBytes ?? defaults.maxSerializedValueBytes
623
+ });
624
+ var compileRedactKeys = (options) => {
625
+ if (!options.redactKeys?.length) return new Set(DEFAULT_REDACT_KEYS);
626
+ return /* @__PURE__ */ new Set([...DEFAULT_REDACT_KEYS, ...options.redactKeys.map((key) => key.toLowerCase())]);
627
+ };
628
+ var isJsonSafeString = (value) => {
629
+ for (let index = 0; index < value.length; index++) {
630
+ const code = value.charCodeAt(index);
631
+ if (code < 32 || code === 34 || code === 92) return false;
632
+ if (code >= 55296 && code <= 56319) {
633
+ const next = value.charCodeAt(index + 1);
634
+ if (index + 1 >= value.length || next < 56320 || next > 57343) return false;
635
+ index++;
636
+ continue;
637
+ }
638
+ if (code >= 56320 && code <= 57343) return false;
627
639
  }
628
- return item;
640
+ return true;
629
641
  };
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;
642
+ var typeMarkerWithValueBytes = (type, jsonSafeValue) => TYPE_MARKER_OPEN.length + type.length + TYPE_MARKER_VALUE_MID.length + Buffer2.byteLength(jsonSafeValue, "utf8") + TYPE_MARKER_CLOSE.length;
643
+ var primitiveFastPath = (value, maxStringLength) => {
644
+ if (value === null) return { value: null, bytes: 4, visitedNodes: 1 };
645
+ if (typeof value === "boolean") return { value, bytes: value ? 4 : 5, visitedNodes: 1 };
646
+ if (typeof value === "number") {
647
+ if (!Number.isFinite(value)) return { value, bytes: 4, visitedNodes: 1 };
648
+ if (Object.is(value, -0)) return { value, bytes: 1, visitedNodes: 1 };
649
+ return { value, bytes: String(value).length, visitedNodes: 1 };
650
+ }
638
651
  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
- });
652
+ if (value.length > maxStringLength || !isJsonSafeString(value)) return void 0;
653
+ return { value, bytes: 2 + Buffer2.byteLength(value, "utf8"), visitedNodes: 1 };
644
654
  }
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 } : {}
655
+ if (typeof value === "undefined") {
656
+ return {
657
+ value: { __rasputin_type: "undefined" },
658
+ bytes: UNDEFINED_MARKER_BYTES,
659
+ visitedNodes: 1
660
+ };
661
+ }
662
+ if (typeof value === "bigint") {
663
+ const digits = value.toString();
664
+ return {
665
+ value: { __rasputin_type: "bigint", value: digits },
666
+ bytes: typeMarkerWithValueBytes("bigint", digits),
667
+ visitedNodes: 1
668
+ };
669
+ }
670
+ if (typeof value === "symbol") {
671
+ const text = String(value);
672
+ if (!isJsonSafeString(text)) return void 0;
673
+ return {
674
+ value: { __rasputin_type: "symbol", value: text },
675
+ bytes: typeMarkerWithValueBytes("symbol", text),
676
+ visitedNodes: 1
677
+ };
678
+ }
679
+ if (typeof value === "function") {
680
+ const name = value.name || "anonymous";
681
+ if (!isJsonSafeString(name)) return void 0;
682
+ return {
683
+ value: { __rasputin_type: "function", value: name },
684
+ bytes: typeMarkerWithValueBytes("function", name),
685
+ visitedNodes: 1
686
+ };
687
+ }
688
+ return void 0;
689
+ };
690
+ var createSafeSerializer = (options = {}) => {
691
+ const limits = compileLimits(options);
692
+ const redactKeys = compileRedactKeys(options);
693
+ const serializeInner = (value, depth, seen, metrics) => {
694
+ metrics.visitedNodes++;
695
+ if (value === null || typeof value === "boolean" || typeof value === "number") return value;
696
+ if (typeof value === "string") {
697
+ if (value.length <= limits.maxStringLength) return value;
698
+ return truncation("max_string_length", {
699
+ __original_length: value.length,
700
+ value: value.slice(0, limits.maxStringLength)
661
701
  });
662
702
  }
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
- ]);
703
+ if (typeof value === "undefined") return typeMarker("undefined");
704
+ if (typeof value === "bigint") return typeMarker("bigint", value.toString());
705
+ if (typeof value === "symbol") return typeMarker("symbol", String(value));
706
+ if (typeof value === "function") return typeMarker("function", value.name || "anonymous");
707
+ if (depth >= limits.maxDepth) return truncation("max_depth");
708
+ if (typeof value !== "object") return typeMarker(typeof value);
709
+ const tracking = seen ?? /* @__PURE__ */ new WeakSet();
710
+ if (tracking.has(value)) return typeMarker("circular");
711
+ tracking.add(value);
712
+ try {
713
+ if (value instanceof Error) {
714
+ return {
715
+ __rasputin_type: "Error",
716
+ name: value.name,
717
+ message: serializeInner(value.message, depth + 1, tracking, metrics),
718
+ ...value.stack ? { stack: serializeInner(value.stack, depth + 1, tracking, metrics) } : {}
719
+ };
672
720
  }
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));
721
+ if (value instanceof Date) return typeMarker("Date", value.toISOString());
722
+ if (Buffer2.isBuffer(value)) {
723
+ return typeMarker("Buffer", {
724
+ byteLength: value.byteLength,
725
+ base64: value.subarray(0, limits.maxStringLength).toString("base64"),
726
+ ...value.byteLength > limits.maxStringLength ? { __rasputin_truncated: true } : {}
727
+ });
684
728
  }
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 }));
729
+ if (value instanceof Promise) return typeMarker("Promise");
730
+ if (value instanceof Map) {
731
+ const entries = [];
732
+ for (const entry of value.entries()) {
733
+ if (entries.length >= limits.maxArrayElements) break;
734
+ entries.push([
735
+ serializeInner(entry[0], depth + 1, tracking, metrics),
736
+ serializeInner(entry[1], depth + 1, tracking, metrics)
737
+ ]);
738
+ }
739
+ return {
740
+ __rasputin_type: "Map",
741
+ entries,
742
+ ...value.size > limits.maxArrayElements ? truncation("max_array_elements", { __original_length: value.size }) : {}
743
+ };
696
744
  }
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 }));
745
+ if (value instanceof Set) {
746
+ const items = [];
747
+ for (const item of value.values()) {
748
+ if (items.length >= limits.maxArrayElements) break;
749
+ items.push(serializeInner(item, depth + 1, tracking, metrics));
750
+ }
751
+ return {
752
+ __rasputin_type: "Set",
753
+ values: items,
754
+ ...value.size > limits.maxArrayElements ? truncation("max_array_elements", { __original_length: value.size }) : {}
755
+ };
756
+ }
757
+ if (Array.isArray(value)) {
758
+ const maxItems = value.length > limits.maxArrayElements ? limits.maxArrayElements - 1 : value.length;
759
+ const items = [];
760
+ for (let index = 0; index < maxItems; index++) {
761
+ items.push(serializeInner(value[index], depth + 1, tracking, metrics));
762
+ }
763
+ if (value.length > limits.maxArrayElements) {
764
+ items.push(truncation("max_array_elements", { __original_length: value.length }));
765
+ }
766
+ return items;
767
+ }
768
+ const prototype = Object.getPrototypeOf(value);
769
+ const serialized = {};
770
+ if (prototype && prototype !== Object.prototype) {
771
+ serialized.__rasputin_type = prototype.constructor?.name ?? "instance";
772
+ }
773
+ const keys = Object.keys(value);
774
+ const maxKeys = keys.length > limits.maxObjectKeys ? limits.maxObjectKeys - 1 : keys.length;
775
+ for (let index = 0; index < maxKeys; index++) {
776
+ const key = keys[index];
777
+ if (key === void 0) continue;
778
+ if (redactKeys.has(key.toLowerCase())) {
779
+ serialized[key] = REDACTED;
780
+ continue;
781
+ }
782
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
783
+ serialized[key] = descriptor && "value" in descriptor ? serializeInner(descriptor.value, depth + 1, tracking, metrics) : typeMarker("accessor");
784
+ }
785
+ if (keys.length > limits.maxObjectKeys) {
786
+ Object.assign(
787
+ serialized,
788
+ truncation("max_object_keys", { __original_length: keys.length })
789
+ );
790
+ }
791
+ return serialized;
792
+ } catch (error) {
793
+ return typeMarker("unserializable", error instanceof Error ? error.message : void 0);
794
+ } finally {
795
+ tracking.delete(value);
712
796
  }
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
797
  };
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 });
798
+ const boundedOversize = (bytes, visitedNodes) => {
799
+ const bounded = truncation("max_serialized_value_bytes", { __serialized_bytes: bytes });
737
800
  return {
738
801
  value: bounded,
739
- bytes: bounded === redacted ? bytes : Buffer2.byteLength(JSON.stringify(bounded), "utf8")
802
+ bytes: Buffer2.byteLength(JSON.stringify(bounded), "utf8"),
803
+ visitedNodes
740
804
  };
741
- } catch {
742
- const value2 = typeMarker("unserializable");
743
- return { value: value2, bytes: Buffer2.byteLength(JSON.stringify(value2), "utf8") };
744
- }
805
+ };
806
+ const serializeWithBytes = (value) => {
807
+ const fast = primitiveFastPath(value, limits.maxStringLength);
808
+ if (fast) {
809
+ return fast.bytes <= limits.maxSerializedValueBytes ? fast : boundedOversize(fast.bytes, fast.visitedNodes);
810
+ }
811
+ const metrics = { visitedNodes: 0 };
812
+ try {
813
+ const serialized = serializeInner(value, 0, void 0, metrics);
814
+ const bytes = Buffer2.byteLength(JSON.stringify(serialized), "utf8");
815
+ if (bytes <= limits.maxSerializedValueBytes) {
816
+ return { value: serialized, bytes, visitedNodes: metrics.visitedNodes };
817
+ }
818
+ return boundedOversize(bytes, metrics.visitedNodes);
819
+ } catch {
820
+ const failed = typeMarker("unserializable");
821
+ return {
822
+ value: failed,
823
+ bytes: Buffer2.byteLength(JSON.stringify(failed), "utf8"),
824
+ visitedNodes: metrics.visitedNodes
825
+ };
826
+ }
827
+ };
828
+ return {
829
+ serialize: (value) => serializeWithBytes(value).value,
830
+ serializeWithBytes
831
+ };
745
832
  };
746
- var safeSerialize = (value, options = {}) => safeSerializeWithBytes(value, options).value;
833
+ var defaultSerializer = createSafeSerializer();
747
834
 
748
835
  // src/execution-recorder/source-exclusions.ts
749
836
  var sourcePathFrom = (definition) => definition.source?.packageRelativePath.replaceAll("\\", "/") ?? definition.name;
@@ -896,6 +983,30 @@ var observeRecordedCallback = (recording, recorderStartedAtNs, callback, onRetur
896
983
  recordExitWork(callbackEndedAtNs);
897
984
  return result;
898
985
  };
986
+ var emptyStats = {
987
+ functionCallsObserved: 0,
988
+ functionCallsCaptured: 0,
989
+ functionCallsExcluded: 0,
990
+ serializedValues: 0,
991
+ serializedValueNodes: 0,
992
+ serializedValueBytes: 0,
993
+ errorSnapshots: 0,
994
+ successfulExecutionsDiscarded: 0,
995
+ truncatedEvents: 0,
996
+ repeatedCallsSuppressed: 0,
997
+ activeExecutions: 0,
998
+ peakActiveExecutions: 0,
999
+ memoryPressureExecutions: 0,
1000
+ memoryPressureDegradations: 0,
1001
+ valuesDroppedByMemoryBudget: 0,
1002
+ eventsDroppedByMemoryBudget: 0,
1003
+ partialErrorSnapshots: 0,
1004
+ measuredExecutions: 0,
1005
+ totalInlineWallTimeMs: 0,
1006
+ maxInlineWallTimeMs: 0,
1007
+ activeEstimatedBytes: 0,
1008
+ peakActiveEstimatedBytes: 0
1009
+ };
899
1010
  var noOpExecution = {
900
1011
  createScope: () => ({
901
1012
  run: (callback) => callback(),
@@ -906,25 +1017,11 @@ var noOpExecution = {
906
1017
  run: (_metadata, callback) => callback(),
907
1018
  getErrorState: () => void 0,
908
1019
  runFunction: (_functionId, _args, callback) => callback(),
1020
+ runFunctionPlans: (_plans, _planIndex, _args, callback) => callback(),
1021
+ prepareFunctionPlans: () => [],
1022
+ runPreparedFunction: (_plan, _args, callback) => callback(),
909
1023
  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
- measuredExecutions: 0,
925
- totalInlineWallTimeMs: 0,
926
- maxInlineWallTimeMs: 0
927
- })
1024
+ getStats: () => ({ ...emptyStats })
928
1025
  };
929
1026
  var mergeExecutionMetadata = (target, metadata) => {
930
1027
  if (metadata.kind) target.kind = metadata.kind;
@@ -947,10 +1044,20 @@ var createExecutionRecorder = (options) => {
947
1044
  maxActiveMemoryBytes,
948
1045
  options.maxSerializedValueBytes ?? 8 * 1024
949
1046
  );
1047
+ const serializer = createSafeSerializer(options);
950
1048
  const excludesSource = createSourceExclusionMatcher(options.excludeSources);
951
1049
  let executionCounter = 0;
1050
+ let nextRuntimePlanId = 1;
1051
+ const internedPlans = /* @__PURE__ */ new Map();
1052
+ const preparedModules = /* @__PURE__ */ new WeakMap();
952
1053
  const errorExecutions = /* @__PURE__ */ new WeakMap();
953
1054
  const stats = {
1055
+ functionCallsObserved: 0,
1056
+ functionCallsCaptured: 0,
1057
+ functionCallsExcluded: 0,
1058
+ serializedValues: 0,
1059
+ serializedValueNodes: 0,
1060
+ serializedValueBytes: 0,
954
1061
  errorSnapshots: 0,
955
1062
  successfulExecutionsDiscarded: 0,
956
1063
  truncatedEvents: 0,
@@ -968,10 +1075,57 @@ var createExecutionRecorder = (options) => {
968
1075
  stats.totalInlineWallTimeMs = Math.round((stats.totalInlineWallTimeMs + inlineWallTimeMs) * 1e4) / 1e4;
969
1076
  stats.maxInlineWallTimeMs = Math.max(stats.maxInlineWallTimeMs, inlineWallTimeMs);
970
1077
  };
971
- const captureValue = (recording, value) => memoryBudget.captureValue(recording.memory, () => safeSerializeWithBytes(value, options));
1078
+ const serializeAndCount = (value) => {
1079
+ const serialized = serializer.serializeWithBytes(value);
1080
+ stats.serializedValues++;
1081
+ stats.serializedValueNodes += serialized.visitedNodes;
1082
+ stats.serializedValueBytes += serialized.bytes;
1083
+ return serialized;
1084
+ };
1085
+ const captureValue = (recording, value) => memoryBudget.captureValue(recording.memory, () => serializeAndCount(value));
1086
+ const preparePlan = (definition) => {
1087
+ const existing = internedPlans.get(definition.functionId);
1088
+ if (existing) return existing;
1089
+ const plan = {
1090
+ runtimePlanId: nextRuntimePlanId++,
1091
+ definition,
1092
+ functionId: definition.functionId,
1093
+ excluded: excludesSource(definition),
1094
+ estimatedCallBytes: estimatedCallBytesFor(definition.functionId)
1095
+ };
1096
+ internedPlans.set(definition.functionId, plan);
1097
+ return plan;
1098
+ };
1099
+ const preparedPlansFor = (plans) => {
1100
+ const cached = preparedModules.get(plans);
1101
+ if (cached) return cached;
1102
+ const prepared = plans.map((encoded) => {
1103
+ const definition = definitionFromEncodedPlan(encoded);
1104
+ return {
1105
+ runtimePlanId: nextRuntimePlanId++,
1106
+ definition,
1107
+ functionId: definition.functionId,
1108
+ excluded: excludesSource(definition),
1109
+ estimatedCallBytes: estimatedCallBytesFor(definition.functionId)
1110
+ };
1111
+ });
1112
+ preparedModules.set(plans, prepared);
1113
+ return prepared;
1114
+ };
1115
+ const snapshotSuppressed = (group, startedAtNs) => ({
1116
+ type: "function_calls_suppressed",
1117
+ functionId: group.functionId,
1118
+ timestampMs: elapsedMs(startedAtNs, group.firstTimestampNs),
1119
+ lastTimestampMs: elapsedMs(startedAtNs, group.lastTimestampNs),
1120
+ suppressedCallCount: group.count,
1121
+ totalDurationMs: roundedMilliseconds(group.totalDurationNs),
1122
+ ...group.parentCallId === void 0 ? {} : { parentCallId: group.parentCallId }
1123
+ });
972
1124
  const stateFrom = (recording, endedAtNs = process.hrtime.bigint()) => {
973
1125
  const recorderStartedAtNs = process.hrtime.bigint();
974
- const events = recording.events.values().map((event) => event.type === "function_calls_suppressed" ? { ...event } : event);
1126
+ const events = recording.events.values().map(
1127
+ (item) => isSuppressionPlaceholder(item) ? snapshotSuppressed(item.group, recording.startedAtNs) : item
1128
+ );
975
1129
  const referencedFunctionIds = new Set(
976
1130
  events.flatMap(
977
1131
  (event) => event.type === "function_enter" || event.type === "function_calls_suppressed" ? [event.functionId] : []
@@ -1020,18 +1174,25 @@ var createExecutionRecorder = (options) => {
1020
1174
  const countDroppedEvents = (recording, droppedBefore) => {
1021
1175
  stats.truncatedEvents += recording.events.dropped - droppedBefore;
1022
1176
  };
1023
- const appendStandaloneEvent = (recording, event) => {
1177
+ const appendSuppressedPlaceholder = (recording, group) => {
1024
1178
  if (!memoryBudget.reserveStandaloneEvent(recording.memory)) return;
1025
1179
  const droppedBefore = recording.events.dropped;
1026
- recording.events.append(event);
1180
+ recording.events.appendSuppressed(group);
1027
1181
  countDroppedEvents(recording, droppedBefore);
1028
1182
  };
1029
- const repeatedCallGroup = (recording, functionId, parentCallId) => {
1030
- let byParent = recording.repeatedCalls.get(functionId);
1031
- if (!byParent) {
1032
- byParent = /* @__PURE__ */ new Map();
1033
- recording.repeatedCalls.set(functionId, byParent);
1183
+ const repeatedCallGroup = (recording, runtimePlanId, parentCallId) => {
1184
+ const slots = recording.repeatedCalls;
1185
+ let slot = slots[runtimePlanId];
1186
+ if (!slot) {
1187
+ slot = {};
1188
+ slots[runtimePlanId] = slot;
1189
+ }
1190
+ if (parentCallId === void 0) {
1191
+ if (!slot.root) slot.root = { observedCalls: 0 };
1192
+ return slot.root;
1034
1193
  }
1194
+ if (!slot.byParent) slot.byParent = /* @__PURE__ */ new Map();
1195
+ const byParent = slot.byParent;
1035
1196
  let group = byParent.get(parentCallId);
1036
1197
  if (!group) {
1037
1198
  group = { observedCalls: 0 };
@@ -1052,14 +1213,14 @@ var createExecutionRecorder = (options) => {
1052
1213
  return {
1053
1214
  // Keep the bounded thrown value even after surrounding capture degrades. A partial
1054
1215
  // snapshot without the exception itself would not be useful for investigation.
1055
- value: safeSerialize(error, options),
1216
+ value: serializeAndCount(error).value,
1056
1217
  commit: () => {
1057
1218
  if (key) recording.capturedErrors.set(key, callId);
1058
1219
  }
1059
1220
  };
1060
1221
  };
1061
- const captureCompletedThrow = (recording, functionId, args, parentCallId, startedAtNs, startedAtMs, error) => {
1062
- if (!memoryBudget.reserveCall(recording.memory, functionId)) {
1222
+ const captureCompletedThrow = (recording, plan, args, parentCallId, startedAtNs, error) => {
1223
+ if (!memoryBudget.reserveCall(recording.memory, plan.estimatedCallBytes)) {
1063
1224
  rememberError(error, recording);
1064
1225
  return;
1065
1226
  }
@@ -1068,8 +1229,8 @@ var createExecutionRecorder = (options) => {
1068
1229
  const enter = {
1069
1230
  type: "function_enter",
1070
1231
  callId,
1071
- functionId,
1072
- timestampMs: startedAtMs,
1232
+ functionId: plan.functionId,
1233
+ timestampMs: elapsedMs(recording.startedAtNs, startedAtNs),
1073
1234
  args: Array.from(args, (argument) => captureValue(recording, argument)),
1074
1235
  ...parentCallId === void 0 ? {} : { parentCallId }
1075
1236
  };
@@ -1082,30 +1243,35 @@ var createExecutionRecorder = (options) => {
1082
1243
  };
1083
1244
  const droppedBefore = recording.events.dropped;
1084
1245
  const retained = recording.events.forceCompletedCall(enter, terminal);
1085
- if (retained) capturedError.commit();
1246
+ if (retained) {
1247
+ stats.functionCallsCaptured++;
1248
+ capturedError.commit();
1249
+ }
1086
1250
  countDroppedEvents(recording, droppedBefore);
1087
1251
  rememberError(error, recording);
1088
1252
  };
1089
- const recordSuppressedCall = (recording, group, functionId, parentCallId, startedAtMs, durationMs) => {
1253
+ const recordSuppressedCall = (recording, group, functionId, parentCallId, startedAtNs, endedAtNs) => {
1090
1254
  stats.repeatedCallsSuppressed++;
1091
- const endedAtMs = startedAtMs + durationMs;
1092
- if (!group.summary) {
1093
- group.summary = {
1094
- type: "function_calls_suppressed",
1255
+ const durationNs = endedAtNs - startedAtNs;
1256
+ if (!group.suppressed) {
1257
+ group.suppressed = {
1258
+ count: 1,
1259
+ firstTimestampNs: startedAtNs,
1260
+ lastTimestampNs: endedAtNs,
1261
+ totalDurationNs: durationNs,
1095
1262
  functionId,
1096
- timestampMs: startedAtMs,
1097
- lastTimestampMs: endedAtMs,
1098
- suppressedCallCount: 1,
1099
- totalDurationMs: durationMs,
1100
1263
  ...parentCallId === void 0 ? {} : { parentCallId }
1101
1264
  };
1102
- appendStandaloneEvent(recording, group.summary);
1265
+ appendSuppressedPlaceholder(recording, group.suppressed);
1103
1266
  return;
1104
1267
  }
1105
- group.summary.lastTimestampMs = endedAtMs;
1106
- group.summary.suppressedCallCount++;
1107
- group.summary.totalDurationMs = Math.round((group.summary.totalDurationMs + durationMs) * 1e4) / 1e4;
1108
- if (!recording.events.contains(group.summary)) appendStandaloneEvent(recording, group.summary);
1268
+ const suppressed = group.suppressed;
1269
+ suppressed.count++;
1270
+ suppressed.lastTimestampNs = endedAtNs;
1271
+ suppressed.totalDurationNs += durationNs;
1272
+ if (!suppressed.retainedSlot) {
1273
+ appendSuppressedPlaceholder(recording, suppressed);
1274
+ }
1109
1275
  };
1110
1276
  const finish = (store, metadata) => {
1111
1277
  const { recording } = store;
@@ -1134,7 +1300,7 @@ var createExecutionRecorder = (options) => {
1134
1300
  },
1135
1301
  events: new CallAwareEventBuffer(maxEventsPerExecution),
1136
1302
  functions: /* @__PURE__ */ new Map(),
1137
- repeatedCalls: /* @__PURE__ */ new Map(),
1303
+ repeatedCalls: [],
1138
1304
  capturedErrors: /* @__PURE__ */ new WeakMap(),
1139
1305
  nextCallId: 0,
1140
1306
  finished: false,
@@ -1202,52 +1368,74 @@ var createExecutionRecorder = (options) => {
1202
1368
  return void 0;
1203
1369
  }
1204
1370
  };
1205
- const runFunction = (input, args, callback) => {
1371
+ const runPreparedFunction = (plan, args, callback) => {
1206
1372
  const store = storage.getStore();
1207
1373
  if (!store || store.recording.finished) return callback();
1208
- const definition = normalizeFunctionDefinition(input);
1209
- const functionId = definition.functionId;
1374
+ stats.functionCallsObserved++;
1210
1375
  const recorderStartedAtNs = process.hrtime.bigint();
1211
- if (store.suppressCapture || excludesSource(definition)) {
1376
+ if (store.suppressCapture || plan.excluded) {
1377
+ stats.functionCallsExcluded++;
1212
1378
  return storage.run({ ...store, suppressCapture: true }, () => {
1213
1379
  addRecorderTime(store.recording, process.hrtime.bigint() - recorderStartedAtNs);
1214
1380
  return callback();
1215
1381
  });
1216
1382
  }
1217
- store.recording.functions.set(functionId, definition);
1218
1383
  const parentCallId = store.currentCallId;
1219
- const group = repeatedCallGroup(store.recording, functionId, parentCallId);
1384
+ const group = repeatedCallGroup(store.recording, plan.runtimePlanId, parentCallId);
1220
1385
  group.observedCalls++;
1386
+ if (group.observedCalls === 1) {
1387
+ store.recording.functions.set(plan.functionId, plan.definition);
1388
+ }
1221
1389
  if (group.observedCalls > maxCapturedCallsPerFunction) {
1222
1390
  const startedAtNs2 = process.hrtime.bigint();
1223
- const startedAtMs = elapsedMs(store.recording.startedAtNs, startedAtNs2);
1224
- const captureSuccess = () => recordSuppressedCall(
1225
- store.recording,
1226
- group,
1227
- functionId,
1228
- parentCallId,
1229
- startedAtMs,
1230
- elapsedMs(startedAtNs2)
1231
- );
1232
- return observeRecordedCallback(
1233
- store.recording,
1234
- recorderStartedAtNs,
1235
- callback,
1236
- captureSuccess,
1237
- (error) => captureCompletedThrow(
1391
+ try {
1392
+ const result = callback();
1393
+ if (isPromiseLike(result)) {
1394
+ addRecorderTime(store.recording, startedAtNs2 - recorderStartedAtNs);
1395
+ return result.then(
1396
+ (value) => {
1397
+ const callbackEndedAtNs2 = process.hrtime.bigint();
1398
+ recordSuppressedCall(
1399
+ store.recording,
1400
+ group,
1401
+ plan.functionId,
1402
+ parentCallId,
1403
+ startedAtNs2,
1404
+ callbackEndedAtNs2
1405
+ );
1406
+ addRecorderTime(store.recording, process.hrtime.bigint() - callbackEndedAtNs2);
1407
+ return value;
1408
+ },
1409
+ (error) => {
1410
+ const callbackEndedAtNs2 = process.hrtime.bigint();
1411
+ captureCompletedThrow(store.recording, plan, args, parentCallId, startedAtNs2, error);
1412
+ addRecorderTime(store.recording, process.hrtime.bigint() - callbackEndedAtNs2);
1413
+ throw error;
1414
+ }
1415
+ );
1416
+ }
1417
+ const callbackEndedAtNs = process.hrtime.bigint();
1418
+ recordSuppressedCall(
1238
1419
  store.recording,
1239
- functionId,
1240
- args,
1420
+ group,
1421
+ plan.functionId,
1241
1422
  parentCallId,
1242
1423
  startedAtNs2,
1243
- startedAtMs,
1244
- error
1245
- )
1246
- );
1424
+ callbackEndedAtNs
1425
+ );
1426
+ addRecorderTime(store.recording, startedAtNs2 - recorderStartedAtNs);
1427
+ addRecorderTime(store.recording, process.hrtime.bigint() - callbackEndedAtNs);
1428
+ return result;
1429
+ } catch (error) {
1430
+ const callbackEndedAtNs = process.hrtime.bigint();
1431
+ captureCompletedThrow(store.recording, plan, args, parentCallId, startedAtNs2, error);
1432
+ addRecorderTime(store.recording, startedAtNs2 - recorderStartedAtNs);
1433
+ addRecorderTime(store.recording, process.hrtime.bigint() - callbackEndedAtNs);
1434
+ throw error;
1435
+ }
1247
1436
  }
1248
1437
  const startedAtNs = process.hrtime.bigint();
1249
1438
  if (!store.recording.events.canStartCall()) {
1250
- const startedAtMs = elapsedMs(store.recording.startedAtNs, startedAtNs);
1251
1439
  const captureSuccess = () => {
1252
1440
  const droppedBefore = store.recording.events.dropped;
1253
1441
  store.recording.events.dropCall();
@@ -1260,19 +1448,11 @@ var createExecutionRecorder = (options) => {
1260
1448
  recorderStartedAtNs,
1261
1449
  callback,
1262
1450
  captureSuccess,
1263
- (error) => captureCompletedThrow(
1264
- store.recording,
1265
- functionId,
1266
- args,
1267
- parentCallId,
1268
- startedAtNs,
1269
- startedAtMs,
1270
- error
1271
- )
1451
+ (error) => captureCompletedThrow(store.recording, plan, args, parentCallId, startedAtNs, error)
1272
1452
  )
1273
1453
  );
1274
1454
  }
1275
- if (!memoryBudget.reserveCall(store.recording.memory, functionId)) {
1455
+ if (!memoryBudget.reserveCall(store.recording.memory, plan.estimatedCallBytes)) {
1276
1456
  return observeRecordedCallback(
1277
1457
  store.recording,
1278
1458
  recorderStartedAtNs,
@@ -1286,7 +1466,7 @@ var createExecutionRecorder = (options) => {
1286
1466
  const enter = {
1287
1467
  type: "function_enter",
1288
1468
  callId,
1289
- functionId,
1469
+ functionId: plan.functionId,
1290
1470
  timestampMs: elapsedMs(store.recording.startedAtNs),
1291
1471
  args: Array.from(args, (argument) => captureValue(store.recording, argument)),
1292
1472
  ...parentCallId === void 0 ? {} : { parentCallId }
@@ -1307,6 +1487,7 @@ var createExecutionRecorder = (options) => {
1307
1487
  durationMs: elapsedMs(startedAtNs),
1308
1488
  returnValue: captureValue(store.recording, value)
1309
1489
  });
1490
+ stats.functionCallsCaptured++;
1310
1491
  },
1311
1492
  (error) => {
1312
1493
  const capturedError = serializedError(store.recording, error, callId);
@@ -1317,16 +1498,24 @@ var createExecutionRecorder = (options) => {
1317
1498
  durationMs: elapsedMs(startedAtNs),
1318
1499
  error: capturedError.value
1319
1500
  });
1501
+ stats.functionCallsCaptured++;
1320
1502
  capturedError.commit();
1321
1503
  rememberError(error, store.recording);
1322
1504
  }
1323
1505
  )
1324
1506
  );
1325
1507
  };
1508
+ const runFunction = (input, args, callback) => runPreparedFunction(preparePlan(normalizeFunctionDefinition(input)), args, callback);
1509
+ const runFunctionPlans = (plans, planIndex, args, callback) => {
1510
+ const plan = preparedPlansFor(plans)[planIndex];
1511
+ return plan ? runPreparedFunction(plan, args, callback) : callback();
1512
+ };
1513
+ const prepareFunctionPlans = (plans) => preparedPlansFor(plans);
1326
1514
  function trace(input, fn) {
1327
1515
  const definition = typeof input === "string" ? manualFunctionDefinition(input) : { ...input, functionId: runtimeFunctionId2(input) };
1516
+ const plan = preparePlan(definition);
1328
1517
  return function traced(...args) {
1329
- return runFunction(definition, args, () => fn.apply(this, args));
1518
+ return runPreparedFunction(plan, args, () => fn.apply(this, args));
1330
1519
  };
1331
1520
  }
1332
1521
  return {
@@ -1334,11 +1523,70 @@ var createExecutionRecorder = (options) => {
1334
1523
  run,
1335
1524
  getErrorState,
1336
1525
  runFunction,
1526
+ runFunctionPlans,
1527
+ prepareFunctionPlans,
1528
+ runPreparedFunction,
1337
1529
  trace,
1338
1530
  getStats: () => ({ ...stats, ...memoryBudget.getStats() })
1339
1531
  };
1340
1532
  };
1341
1533
 
1534
+ // src/execution-recorder/automatic-runtime.ts
1535
+ var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v2";
1536
+ var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
1537
+ var passthrough = (_planIndex, _args, callback) => callback();
1538
+ var bindPassthrough = (module) => {
1539
+ module.cell.run = passthrough;
1540
+ };
1541
+ var isRegistry = (value) => Array.isArray(Reflect.get(value, "runtimes")) && Array.isArray(Reflect.get(value, "modules")) && typeof Reflect.get(value, "register") === "function";
1542
+ var registerModule = (registry, plans) => {
1543
+ const cell = { run: passthrough };
1544
+ const module = { plans, cell };
1545
+ registry.modules.push(module);
1546
+ registry.runtimes.at(-1)?.bind(module);
1547
+ return (planIndex, args, callback) => cell.run(planIndex, args, callback);
1548
+ };
1549
+ var createRegistry = () => {
1550
+ const registry = {
1551
+ runtimes: [],
1552
+ modules: [],
1553
+ register(plans) {
1554
+ return registerModule(registry, plans);
1555
+ }
1556
+ };
1557
+ return registry;
1558
+ };
1559
+ var getAutomaticRuntimeRegistry = () => {
1560
+ const existing = Reflect.get(globalThis, runtimeSymbol);
1561
+ if (existing && typeof existing === "object" && isRegistry(existing)) return existing;
1562
+ const registry = createRegistry();
1563
+ Reflect.set(globalThis, runtimeSymbol, registry);
1564
+ return registry;
1565
+ };
1566
+ var installAutomaticExecutionRuntime = (execution) => {
1567
+ const registry = getAutomaticRuntimeRegistry();
1568
+ const runtime = {
1569
+ bind(module) {
1570
+ const prepared = execution.prepareFunctionPlans(module.plans);
1571
+ module.cell.run = (planIndex, args, callback) => {
1572
+ const plan = prepared[planIndex];
1573
+ return plan ? execution.runPreparedFunction(plan, args, callback) : callback();
1574
+ };
1575
+ }
1576
+ };
1577
+ registry.runtimes.push(runtime);
1578
+ for (const module of registry.modules) runtime.bind(module);
1579
+ return () => {
1580
+ const index = registry.runtimes.lastIndexOf(runtime);
1581
+ if (index >= 0) registry.runtimes.splice(index, 1);
1582
+ const current = registry.runtimes.at(-1);
1583
+ for (const module of registry.modules) {
1584
+ if (current) current.bind(module);
1585
+ else bindPassthrough(module);
1586
+ }
1587
+ };
1588
+ };
1589
+
1342
1590
  // src/install-global-handlers.ts
1343
1591
  var removeProcessListener = process.off.bind(process);
1344
1592
  var installGlobalHandlers = (client, options = {}) => {
@@ -1388,6 +1636,7 @@ var installGlobalHandlers = (client, options = {}) => {
1388
1636
  };
1389
1637
 
1390
1638
  // src/rasputin-init.ts
1639
+ import { join } from "node:path";
1391
1640
  import {
1392
1641
  createClient,
1393
1642
  isClientEnabled
@@ -1395,7 +1644,7 @@ import {
1395
1644
 
1396
1645
  // src/sdk-meta.ts
1397
1646
  var SDK_NAME = "@rasputin-ai/node";
1398
- var SDK_VERSION = "0.4.1";
1647
+ var SDK_VERSION = "0.5.0-alpha.2";
1399
1648
 
1400
1649
  // src/rasputin-init.ts
1401
1650
  var withExecution = (client, execution, installRuntime, manifest) => {
@@ -1436,6 +1685,9 @@ var RasputinInit = (options) => {
1436
1685
  ...options.executionRecorder,
1437
1686
  enabled: recorderEnabled
1438
1687
  });
1688
+ loadInstrumentationManifestIfPresent(
1689
+ options.instrumentationManifestPath ?? process.env.RASPUTIN_INSTRUMENTATION_MANIFEST ?? join(process.cwd(), ".rasputin", "instrumentation-manifest.json")
1690
+ );
1439
1691
  const wrappedClient = withExecution(client, execution, recorderEnabled, {
1440
1692
  projectApiKey: options.projectApiKey,
1441
1693
  release: options.release,