@rasputin-ai/node 0.4.0 → 0.5.0-alpha.1

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 (35) hide show
  1. package/README.md +185 -159
  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/execution-recorder/automatic-runtime.d.ts +12 -10
  13. package/dist/execution-recorder/automatic-runtime.d.ts.map +1 -1
  14. package/dist/execution-recorder/call-aware-event-buffer.d.ts +2 -1
  15. package/dist/execution-recorder/call-aware-event-buffer.d.ts.map +1 -1
  16. package/dist/execution-recorder/execution-recorder-types.d.ts +20 -0
  17. package/dist/execution-recorder/execution-recorder-types.d.ts.map +1 -1
  18. package/dist/execution-recorder/execution-recorder.d.ts.map +1 -1
  19. package/dist/execution-recorder/function-plan.d.ts +26 -0
  20. package/dist/execution-recorder/function-plan.d.ts.map +1 -0
  21. package/dist/execution-recorder/recorder-memory-budget.d.ts +7 -1
  22. package/dist/execution-recorder/recorder-memory-budget.d.ts.map +1 -1
  23. package/dist/execution-recorder/safe-serialize.d.ts +7 -0
  24. package/dist/execution-recorder/safe-serialize.d.ts.map +1 -1
  25. package/dist/index.js +450 -257
  26. package/dist/instrument/build.d.ts +2 -0
  27. package/dist/instrument/build.d.ts.map +1 -0
  28. package/dist/instrument/build.js +707 -0
  29. package/dist/instrument/bun.js +41 -21
  30. package/dist/instrument/node.js +41 -21
  31. package/dist/rasputin-init.d.ts +5 -5
  32. package/dist/rasputin-init.d.ts.map +1 -1
  33. package/dist/sdk-meta.d.ts +1 -1
  34. package/dist/sdk-meta.d.ts.map +1 -1
  35. 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
@@ -395,6 +393,7 @@ var CallAwareEventBuffer = class {
395
393
  }
396
394
  items = [];
397
395
  openCallIds = /* @__PURE__ */ new Set();
396
+ retainedSummaries = /* @__PURE__ */ new WeakSet();
398
397
  dropped = 0;
399
398
  canStartCall() {
400
399
  return this.items.length + this.openCallIds.size + 2 <= this.capacity;
@@ -417,8 +416,12 @@ var CallAwareEventBuffer = class {
417
416
  return false;
418
417
  }
419
418
  this.items.push(event);
419
+ if (event.type === "function_calls_suppressed") this.retainedSummaries.add(event);
420
420
  return true;
421
421
  }
422
+ hasSummary(event) {
423
+ return this.retainedSummaries.has(event);
424
+ }
422
425
  forceCompletedCall(enter, terminal) {
423
426
  while (this.items.length + this.openCallIds.size + 2 > this.capacity) {
424
427
  if (!this.evictCompletedLeafOrSummary()) {
@@ -432,14 +435,13 @@ var CallAwareEventBuffer = class {
432
435
  values() {
433
436
  return [...this.items];
434
437
  }
435
- contains(target) {
436
- return this.items.includes(target);
437
- }
438
438
  evictCompletedLeafOrSummary() {
439
439
  const summaryIndex = this.items.findIndex(
440
440
  (event) => event.type === "function_calls_suppressed"
441
441
  );
442
442
  if (summaryIndex >= 0) {
443
+ const summary = this.items[summaryIndex];
444
+ if (summary) this.retainedSummaries.delete(summary);
443
445
  this.items.splice(summaryIndex, 1);
444
446
  this.dropped++;
445
447
  return true;
@@ -469,9 +471,7 @@ var CallAwareEventBuffer = class {
469
471
  };
470
472
 
471
473
  // src/execution-recorder/recorder-memory-budget.ts
472
- import { Buffer } from "node:buffer";
473
474
  var EXECUTION_BASE_ESTIMATED_BYTES = 1024;
474
- var EVENT_PAIR_BASE_ESTIMATED_BYTES = 512;
475
475
  var SUMMARY_EVENT_BASE_ESTIMATED_BYTES = 256;
476
476
  var VALUE_HEAP_ESTIMATE_MULTIPLIER = 2;
477
477
  var MEMORY_BUDGET_VALUE_MARKER = {
@@ -525,12 +525,11 @@ var RecorderMemoryBudget = class {
525
525
  }
526
526
  return serialized.value;
527
527
  }
528
- reserveCall(allocation, functionId) {
528
+ reserveCall(allocation, estimatedBytes) {
529
529
  if (allocation.mode === "metadata-only") {
530
530
  this.dropEvents(allocation, 2);
531
531
  return false;
532
532
  }
533
- const estimatedBytes = EVENT_PAIR_BASE_ESTIMATED_BYTES + Buffer.byteLength(functionId, "utf8") * 2;
534
533
  if (this.reserve(allocation, estimatedBytes)) return true;
535
534
  this.markPressure(allocation, "metadata-only");
536
535
  this.dropEvents(allocation, 2);
@@ -590,7 +589,7 @@ var RecorderMemoryBudget = class {
590
589
 
591
590
  // src/execution-recorder/safe-serialize.ts
592
591
  import { Buffer as Buffer2 } from "node:buffer";
593
- var DEFAULT_REDACT_KEYS = /* @__PURE__ */ new Set([
592
+ var DEFAULT_REDACT_KEYS = [
594
593
  "password",
595
594
  "token",
596
595
  "authorization",
@@ -598,7 +597,7 @@ var DEFAULT_REDACT_KEYS = /* @__PURE__ */ new Set([
598
597
  "secret",
599
598
  "apikey",
600
599
  "creditcard"
601
- ]);
600
+ ];
602
601
  var defaults = {
603
602
  maxDepth: 3,
604
603
  maxObjectKeys: 30,
@@ -615,135 +614,155 @@ var typeMarker = (type, value) => ({
615
614
  __rasputin_type: type,
616
615
  ...value === void 0 ? {} : { value }
617
616
  });
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) } : {}
617
+ var REDACTED = "[RASPUTIN_REDACTED]";
618
+ var compileLimits = (options) => ({
619
+ maxDepth: options.maxDepth ?? defaults.maxDepth,
620
+ maxObjectKeys: options.maxObjectKeys ?? defaults.maxObjectKeys,
621
+ maxArrayElements: options.maxArrayElements ?? defaults.maxArrayElements,
622
+ maxStringLength: options.maxStringLength ?? defaults.maxStringLength,
623
+ maxSerializedValueBytes: options.maxSerializedValueBytes ?? defaults.maxSerializedValueBytes
635
624
  });
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 } : {}
625
+ var compileRedactKeys = (options) => {
626
+ if (!options.redactKeys?.length) return new Set(DEFAULT_REDACT_KEYS);
627
+ return /* @__PURE__ */ new Set([...DEFAULT_REDACT_KEYS, ...options.redactKeys.map((key) => key.toLowerCase())]);
628
+ };
629
+ var createSafeSerializer = (options = {}) => {
630
+ const limits = compileLimits(options);
631
+ const redactKeys = compileRedactKeys(options);
632
+ const serializeInner = (value, depth, seen, metrics) => {
633
+ metrics.visitedNodes++;
634
+ if (value === null || typeof value === "boolean" || typeof value === "number") return value;
635
+ if (typeof value === "string") {
636
+ if (value.length <= limits.maxStringLength) return value;
637
+ return truncation("max_string_length", {
638
+ __original_length: value.length,
639
+ value: value.slice(0, limits.maxStringLength)
661
640
  });
662
641
  }
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
- ]);
642
+ if (typeof value === "undefined") return typeMarker("undefined");
643
+ if (typeof value === "bigint") return typeMarker("bigint", value.toString());
644
+ if (typeof value === "symbol") return typeMarker("symbol", String(value));
645
+ if (typeof value === "function") return typeMarker("function", value.name || "anonymous");
646
+ if (depth >= limits.maxDepth) return truncation("max_depth");
647
+ if (typeof value !== "object") return typeMarker(typeof value);
648
+ const tracking = seen ?? /* @__PURE__ */ new WeakSet();
649
+ if (tracking.has(value)) return typeMarker("circular");
650
+ tracking.add(value);
651
+ try {
652
+ if (value instanceof Error) {
653
+ return {
654
+ __rasputin_type: "Error",
655
+ name: value.name,
656
+ message: serializeInner(value.message, depth + 1, tracking, metrics),
657
+ ...value.stack ? { stack: serializeInner(value.stack, depth + 1, tracking, metrics) } : {}
658
+ };
672
659
  }
673
- return {
674
- __rasputin_type: "Map",
675
- entries,
676
- ...value.size > limits.maxArrayElements ? truncation("max_array_elements", { __original_length: value.size }) : {}
677
- };
660
+ if (value instanceof Date) return typeMarker("Date", value.toISOString());
661
+ if (Buffer2.isBuffer(value)) {
662
+ return typeMarker("Buffer", {
663
+ byteLength: value.byteLength,
664
+ base64: value.subarray(0, limits.maxStringLength).toString("base64"),
665
+ ...value.byteLength > limits.maxStringLength ? { __rasputin_truncated: true } : {}
666
+ });
667
+ }
668
+ if (value instanceof Promise) return typeMarker("Promise");
669
+ if (value instanceof Map) {
670
+ const entries = [];
671
+ for (const entry of value.entries()) {
672
+ if (entries.length >= limits.maxArrayElements) break;
673
+ entries.push([
674
+ serializeInner(entry[0], depth + 1, tracking, metrics),
675
+ serializeInner(entry[1], depth + 1, tracking, metrics)
676
+ ]);
677
+ }
678
+ return {
679
+ __rasputin_type: "Map",
680
+ entries,
681
+ ...value.size > limits.maxArrayElements ? truncation("max_array_elements", { __original_length: value.size }) : {}
682
+ };
683
+ }
684
+ if (value instanceof Set) {
685
+ const items = [];
686
+ for (const item of value.values()) {
687
+ if (items.length >= limits.maxArrayElements) break;
688
+ items.push(serializeInner(item, depth + 1, tracking, metrics));
689
+ }
690
+ return {
691
+ __rasputin_type: "Set",
692
+ values: items,
693
+ ...value.size > limits.maxArrayElements ? truncation("max_array_elements", { __original_length: value.size }) : {}
694
+ };
695
+ }
696
+ if (Array.isArray(value)) {
697
+ const maxItems = value.length > limits.maxArrayElements ? limits.maxArrayElements - 1 : value.length;
698
+ const items = [];
699
+ for (let index = 0; index < maxItems; index++) {
700
+ items.push(serializeInner(value[index], depth + 1, tracking, metrics));
701
+ }
702
+ if (value.length > limits.maxArrayElements) {
703
+ items.push(truncation("max_array_elements", { __original_length: value.length }));
704
+ }
705
+ return items;
706
+ }
707
+ const prototype = Object.getPrototypeOf(value);
708
+ const serialized = {};
709
+ if (prototype && prototype !== Object.prototype) {
710
+ serialized.__rasputin_type = prototype.constructor?.name ?? "instance";
711
+ }
712
+ const keys = Object.keys(value);
713
+ const maxKeys = keys.length > limits.maxObjectKeys ? limits.maxObjectKeys - 1 : keys.length;
714
+ for (let index = 0; index < maxKeys; index++) {
715
+ const key = keys[index];
716
+ if (key === void 0) continue;
717
+ if (redactKeys.has(key.toLowerCase())) {
718
+ serialized[key] = REDACTED;
719
+ continue;
720
+ }
721
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
722
+ serialized[key] = descriptor && "value" in descriptor ? serializeInner(descriptor.value, depth + 1, tracking, metrics) : typeMarker("accessor");
723
+ }
724
+ if (keys.length > limits.maxObjectKeys) {
725
+ Object.assign(
726
+ serialized,
727
+ truncation("max_object_keys", { __original_length: keys.length })
728
+ );
729
+ }
730
+ return serialized;
731
+ } catch (error) {
732
+ return typeMarker("unserializable", error instanceof Error ? error.message : void 0);
733
+ } finally {
734
+ tracking.delete(value);
678
735
  }
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));
736
+ };
737
+ const serializeWithBytes = (value) => {
738
+ const metrics = { visitedNodes: 0 };
739
+ try {
740
+ const serialized = serializeInner(value, 0, void 0, metrics);
741
+ const bytes = Buffer2.byteLength(JSON.stringify(serialized), "utf8");
742
+ if (bytes <= limits.maxSerializedValueBytes) {
743
+ return { value: serialized, bytes, visitedNodes: metrics.visitedNodes };
684
744
  }
745
+ const bounded = truncation("max_serialized_value_bytes", { __serialized_bytes: bytes });
685
746
  return {
686
- __rasputin_type: "Set",
687
- values: items,
688
- ...value.size > limits.maxArrayElements ? truncation("max_array_elements", { __original_length: value.size }) : {}
747
+ value: bounded,
748
+ bytes: Buffer2.byteLength(JSON.stringify(bounded), "utf8"),
749
+ visitedNodes: metrics.visitedNodes
750
+ };
751
+ } catch {
752
+ const failed = typeMarker("unserializable");
753
+ return {
754
+ value: failed,
755
+ bytes: Buffer2.byteLength(JSON.stringify(failed), "utf8"),
756
+ visitedNodes: metrics.visitedNodes
689
757
  };
690
758
  }
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
759
  };
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
- }
760
+ return {
761
+ serialize: (value) => serializeWithBytes(value).value,
762
+ serializeWithBytes
763
+ };
745
764
  };
746
- var safeSerialize = (value, options = {}) => safeSerializeWithBytes(value, options).value;
765
+ var defaultSerializer = createSafeSerializer();
747
766
 
748
767
  // src/execution-recorder/source-exclusions.ts
749
768
  var sourcePathFrom = (definition) => definition.source?.packageRelativePath.replaceAll("\\", "/") ?? definition.name;
@@ -896,6 +915,30 @@ var observeRecordedCallback = (recording, recorderStartedAtNs, callback, onRetur
896
915
  recordExitWork(callbackEndedAtNs);
897
916
  return result;
898
917
  };
918
+ var emptyStats = {
919
+ functionCallsObserved: 0,
920
+ functionCallsCaptured: 0,
921
+ functionCallsExcluded: 0,
922
+ serializedValues: 0,
923
+ serializedValueNodes: 0,
924
+ serializedValueBytes: 0,
925
+ errorSnapshots: 0,
926
+ successfulExecutionsDiscarded: 0,
927
+ truncatedEvents: 0,
928
+ repeatedCallsSuppressed: 0,
929
+ activeExecutions: 0,
930
+ peakActiveExecutions: 0,
931
+ memoryPressureExecutions: 0,
932
+ memoryPressureDegradations: 0,
933
+ valuesDroppedByMemoryBudget: 0,
934
+ eventsDroppedByMemoryBudget: 0,
935
+ partialErrorSnapshots: 0,
936
+ measuredExecutions: 0,
937
+ totalInlineWallTimeMs: 0,
938
+ maxInlineWallTimeMs: 0,
939
+ activeEstimatedBytes: 0,
940
+ peakActiveEstimatedBytes: 0
941
+ };
899
942
  var noOpExecution = {
900
943
  createScope: () => ({
901
944
  run: (callback) => callback(),
@@ -906,22 +949,10 @@ var noOpExecution = {
906
949
  run: (_metadata, callback) => callback(),
907
950
  getErrorState: () => void 0,
908
951
  runFunction: (_functionId, _args, callback) => callback(),
952
+ runFunctionPlans: (_plans, _planIndex, _args, callback) => callback(),
953
+ runPreparedFunction: (_plan, _args, callback) => callback(),
909
954
  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
- })
955
+ getStats: () => ({ ...emptyStats })
925
956
  };
926
957
  var mergeExecutionMetadata = (target, metadata) => {
927
958
  if (metadata.kind) target.kind = metadata.kind;
@@ -944,21 +975,83 @@ var createExecutionRecorder = (options) => {
944
975
  maxActiveMemoryBytes,
945
976
  options.maxSerializedValueBytes ?? 8 * 1024
946
977
  );
978
+ const serializer = createSafeSerializer(options);
947
979
  const excludesSource = createSourceExclusionMatcher(options.excludeSources);
948
980
  let executionCounter = 0;
981
+ let nextRuntimePlanId = 1;
982
+ const internedPlans = /* @__PURE__ */ new Map();
983
+ const preparedModules = /* @__PURE__ */ new WeakMap();
949
984
  const errorExecutions = /* @__PURE__ */ new WeakMap();
950
985
  const stats = {
986
+ functionCallsObserved: 0,
987
+ functionCallsCaptured: 0,
988
+ functionCallsExcluded: 0,
989
+ serializedValues: 0,
990
+ serializedValueNodes: 0,
991
+ serializedValueBytes: 0,
951
992
  errorSnapshots: 0,
952
993
  successfulExecutionsDiscarded: 0,
953
994
  truncatedEvents: 0,
954
995
  repeatedCallsSuppressed: 0,
955
996
  activeExecutions: 0,
956
997
  peakActiveExecutions: 0,
957
- partialErrorSnapshots: 0
998
+ partialErrorSnapshots: 0,
999
+ measuredExecutions: 0,
1000
+ totalInlineWallTimeMs: 0,
1001
+ maxInlineWallTimeMs: 0
1002
+ };
1003
+ const recordInlineTiming = (recording) => {
1004
+ const inlineWallTimeMs = roundedMilliseconds(recording.inlineRecorderTimeNs);
1005
+ stats.measuredExecutions++;
1006
+ stats.totalInlineWallTimeMs = Math.round((stats.totalInlineWallTimeMs + inlineWallTimeMs) * 1e4) / 1e4;
1007
+ stats.maxInlineWallTimeMs = Math.max(stats.maxInlineWallTimeMs, inlineWallTimeMs);
1008
+ };
1009
+ const serializeAndCount = (value) => {
1010
+ const serialized = serializer.serializeWithBytes(value);
1011
+ stats.serializedValues++;
1012
+ stats.serializedValueNodes += serialized.visitedNodes;
1013
+ stats.serializedValueBytes += serialized.bytes;
1014
+ return serialized;
1015
+ };
1016
+ const captureValue = (recording, value) => memoryBudget.captureValue(recording.memory, () => serializeAndCount(value));
1017
+ const preparePlan = (definition) => {
1018
+ const existing = internedPlans.get(definition.functionId);
1019
+ if (existing) return existing;
1020
+ const plan = {
1021
+ runtimePlanId: nextRuntimePlanId++,
1022
+ definition,
1023
+ functionId: definition.functionId,
1024
+ excluded: excludesSource(definition),
1025
+ estimatedCallBytes: estimatedCallBytesFor(definition.functionId)
1026
+ };
1027
+ internedPlans.set(definition.functionId, plan);
1028
+ return plan;
1029
+ };
1030
+ const preparedPlansFor = (plans) => {
1031
+ const cached = preparedModules.get(plans);
1032
+ if (cached) return cached;
1033
+ const prepared = plans.map((encoded) => {
1034
+ const definition = definitionFromEncodedPlan(encoded);
1035
+ return {
1036
+ runtimePlanId: nextRuntimePlanId++,
1037
+ definition,
1038
+ functionId: definition.functionId,
1039
+ excluded: excludesSource(definition),
1040
+ estimatedCallBytes: estimatedCallBytesFor(definition.functionId)
1041
+ };
1042
+ });
1043
+ preparedModules.set(plans, prepared);
1044
+ return prepared;
958
1045
  };
959
- const captureValue = (recording, value) => memoryBudget.captureValue(recording.memory, () => safeSerializeWithBytes(value, options));
960
1046
  const stateFrom = (recording, endedAtNs = process.hrtime.bigint()) => {
961
1047
  const recorderStartedAtNs = process.hrtime.bigint();
1048
+ for (const byParent of recording.repeatedCalls.values()) {
1049
+ for (const group of byParent.values()) {
1050
+ if (group.summary) {
1051
+ group.summary.totalDurationMs = roundedMilliseconds(group.totalDurationNs);
1052
+ }
1053
+ }
1054
+ }
962
1055
  const events = recording.events.values().map((event) => event.type === "function_calls_suppressed" ? { ...event } : event);
963
1056
  const referencedFunctionIds = new Set(
964
1057
  events.flatMap(
@@ -1014,15 +1107,15 @@ var createExecutionRecorder = (options) => {
1014
1107
  recording.events.append(event);
1015
1108
  countDroppedEvents(recording, droppedBefore);
1016
1109
  };
1017
- const repeatedCallGroup = (recording, functionId, parentCallId) => {
1018
- let byParent = recording.repeatedCalls.get(functionId);
1110
+ const repeatedCallGroup = (recording, runtimePlanId, parentCallId) => {
1111
+ let byParent = recording.repeatedCalls.get(runtimePlanId);
1019
1112
  if (!byParent) {
1020
1113
  byParent = /* @__PURE__ */ new Map();
1021
- recording.repeatedCalls.set(functionId, byParent);
1114
+ recording.repeatedCalls.set(runtimePlanId, byParent);
1022
1115
  }
1023
1116
  let group = byParent.get(parentCallId);
1024
1117
  if (!group) {
1025
- group = { observedCalls: 0 };
1118
+ group = { observedCalls: 0, totalDurationNs: 0n };
1026
1119
  byParent.set(parentCallId, group);
1027
1120
  }
1028
1121
  return group;
@@ -1040,14 +1133,14 @@ var createExecutionRecorder = (options) => {
1040
1133
  return {
1041
1134
  // Keep the bounded thrown value even after surrounding capture degrades. A partial
1042
1135
  // snapshot without the exception itself would not be useful for investigation.
1043
- value: safeSerialize(error, options),
1136
+ value: serializeAndCount(error).value,
1044
1137
  commit: () => {
1045
1138
  if (key) recording.capturedErrors.set(key, callId);
1046
1139
  }
1047
1140
  };
1048
1141
  };
1049
- const captureCompletedThrow = (recording, functionId, args, parentCallId, startedAtNs, startedAtMs, error) => {
1050
- if (!memoryBudget.reserveCall(recording.memory, functionId)) {
1142
+ const captureCompletedThrow = (recording, plan, args, parentCallId, startedAtNs, startedAtMs, error) => {
1143
+ if (!memoryBudget.reserveCall(recording.memory, plan.estimatedCallBytes)) {
1051
1144
  rememberError(error, recording);
1052
1145
  return;
1053
1146
  }
@@ -1056,7 +1149,7 @@ var createExecutionRecorder = (options) => {
1056
1149
  const enter = {
1057
1150
  type: "function_enter",
1058
1151
  callId,
1059
- functionId,
1152
+ functionId: plan.functionId,
1060
1153
  timestampMs: startedAtMs,
1061
1154
  args: Array.from(args, (argument) => captureValue(recording, argument)),
1062
1155
  ...parentCallId === void 0 ? {} : { parentCallId }
@@ -1070,13 +1163,17 @@ var createExecutionRecorder = (options) => {
1070
1163
  };
1071
1164
  const droppedBefore = recording.events.dropped;
1072
1165
  const retained = recording.events.forceCompletedCall(enter, terminal);
1073
- if (retained) capturedError.commit();
1166
+ if (retained) {
1167
+ stats.functionCallsCaptured++;
1168
+ capturedError.commit();
1169
+ }
1074
1170
  countDroppedEvents(recording, droppedBefore);
1075
1171
  rememberError(error, recording);
1076
1172
  };
1077
- const recordSuppressedCall = (recording, group, functionId, parentCallId, startedAtMs, durationMs) => {
1173
+ const recordSuppressedCall = (recording, group, functionId, parentCallId, startedAtMs, startedAtNs, endedAtNs) => {
1078
1174
  stats.repeatedCallsSuppressed++;
1079
- const endedAtMs = startedAtMs + durationMs;
1175
+ group.totalDurationNs += endedAtNs - startedAtNs;
1176
+ const endedAtMs = elapsedMs(recording.startedAtNs, endedAtNs);
1080
1177
  if (!group.summary) {
1081
1178
  group.summary = {
1082
1179
  type: "function_calls_suppressed",
@@ -1084,7 +1181,7 @@ var createExecutionRecorder = (options) => {
1084
1181
  timestampMs: startedAtMs,
1085
1182
  lastTimestampMs: endedAtMs,
1086
1183
  suppressedCallCount: 1,
1087
- totalDurationMs: durationMs,
1184
+ totalDurationMs: 0,
1088
1185
  ...parentCallId === void 0 ? {} : { parentCallId }
1089
1186
  };
1090
1187
  appendStandaloneEvent(recording, group.summary);
@@ -1092,8 +1189,9 @@ var createExecutionRecorder = (options) => {
1092
1189
  }
1093
1190
  group.summary.lastTimestampMs = endedAtMs;
1094
1191
  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);
1192
+ if (!recording.events.hasSummary(group.summary)) {
1193
+ appendStandaloneEvent(recording, group.summary);
1194
+ }
1097
1195
  };
1098
1196
  const finish = (store, metadata) => {
1099
1197
  const { recording } = store;
@@ -1106,6 +1204,7 @@ var createExecutionRecorder = (options) => {
1106
1204
  if (!recording.hasError) stats.successfulExecutionsDiscarded++;
1107
1205
  recording.finishedAtNs = process.hrtime.bigint();
1108
1206
  addRecorderTime(recording, recording.finishedAtNs - recorderStartedAtNs);
1207
+ recordInlineTiming(recording);
1109
1208
  };
1110
1209
  const createScope = (metadata) => {
1111
1210
  const recorderStartedAtNs = process.hrtime.bigint();
@@ -1189,48 +1288,90 @@ var createExecutionRecorder = (options) => {
1189
1288
  return void 0;
1190
1289
  }
1191
1290
  };
1192
- const runFunction = (input, args, callback) => {
1291
+ const runPreparedFunction = (plan, args, callback) => {
1193
1292
  const store = storage.getStore();
1194
1293
  if (!store || store.recording.finished) return callback();
1195
- const definition = normalizeFunctionDefinition(input);
1196
- const functionId = definition.functionId;
1294
+ stats.functionCallsObserved++;
1197
1295
  const recorderStartedAtNs = process.hrtime.bigint();
1198
- if (store.suppressCapture || excludesSource(definition)) {
1296
+ if (store.suppressCapture || plan.excluded) {
1297
+ stats.functionCallsExcluded++;
1199
1298
  return storage.run({ ...store, suppressCapture: true }, () => {
1200
1299
  addRecorderTime(store.recording, process.hrtime.bigint() - recorderStartedAtNs);
1201
1300
  return callback();
1202
1301
  });
1203
1302
  }
1204
- store.recording.functions.set(functionId, definition);
1205
1303
  const parentCallId = store.currentCallId;
1206
- const group = repeatedCallGroup(store.recording, functionId, parentCallId);
1304
+ const group = repeatedCallGroup(store.recording, plan.runtimePlanId, parentCallId);
1207
1305
  group.observedCalls++;
1306
+ if (group.observedCalls === 1) {
1307
+ store.recording.functions.set(plan.functionId, plan.definition);
1308
+ }
1208
1309
  if (group.observedCalls > maxCapturedCallsPerFunction) {
1209
1310
  const startedAtNs2 = process.hrtime.bigint();
1210
1311
  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(
1312
+ try {
1313
+ const result = callback();
1314
+ if (isPromiseLike(result)) {
1315
+ addRecorderTime(store.recording, startedAtNs2 - recorderStartedAtNs);
1316
+ return result.then(
1317
+ (value) => {
1318
+ const callbackEndedAtNs2 = process.hrtime.bigint();
1319
+ recordSuppressedCall(
1320
+ store.recording,
1321
+ group,
1322
+ plan.functionId,
1323
+ parentCallId,
1324
+ startedAtMs,
1325
+ startedAtNs2,
1326
+ callbackEndedAtNs2
1327
+ );
1328
+ addRecorderTime(store.recording, process.hrtime.bigint() - callbackEndedAtNs2);
1329
+ return value;
1330
+ },
1331
+ (error) => {
1332
+ const callbackEndedAtNs2 = process.hrtime.bigint();
1333
+ captureCompletedThrow(
1334
+ store.recording,
1335
+ plan,
1336
+ args,
1337
+ parentCallId,
1338
+ startedAtNs2,
1339
+ startedAtMs,
1340
+ error
1341
+ );
1342
+ addRecorderTime(store.recording, process.hrtime.bigint() - callbackEndedAtNs2);
1343
+ throw error;
1344
+ }
1345
+ );
1346
+ }
1347
+ const callbackEndedAtNs = process.hrtime.bigint();
1348
+ recordSuppressedCall(
1349
+ store.recording,
1350
+ group,
1351
+ plan.functionId,
1352
+ parentCallId,
1353
+ startedAtMs,
1354
+ startedAtNs2,
1355
+ callbackEndedAtNs
1356
+ );
1357
+ addRecorderTime(store.recording, startedAtNs2 - recorderStartedAtNs);
1358
+ addRecorderTime(store.recording, process.hrtime.bigint() - callbackEndedAtNs);
1359
+ return result;
1360
+ } catch (error) {
1361
+ const callbackEndedAtNs = process.hrtime.bigint();
1362
+ captureCompletedThrow(
1225
1363
  store.recording,
1226
- functionId,
1364
+ plan,
1227
1365
  args,
1228
1366
  parentCallId,
1229
1367
  startedAtNs2,
1230
1368
  startedAtMs,
1231
1369
  error
1232
- )
1233
- );
1370
+ );
1371
+ addRecorderTime(store.recording, startedAtNs2 - recorderStartedAtNs);
1372
+ addRecorderTime(store.recording, process.hrtime.bigint() - callbackEndedAtNs);
1373
+ throw error;
1374
+ }
1234
1375
  }
1235
1376
  const startedAtNs = process.hrtime.bigint();
1236
1377
  if (!store.recording.events.canStartCall()) {
@@ -1249,7 +1390,7 @@ var createExecutionRecorder = (options) => {
1249
1390
  captureSuccess,
1250
1391
  (error) => captureCompletedThrow(
1251
1392
  store.recording,
1252
- functionId,
1393
+ plan,
1253
1394
  args,
1254
1395
  parentCallId,
1255
1396
  startedAtNs,
@@ -1259,7 +1400,7 @@ var createExecutionRecorder = (options) => {
1259
1400
  )
1260
1401
  );
1261
1402
  }
1262
- if (!memoryBudget.reserveCall(store.recording.memory, functionId)) {
1403
+ if (!memoryBudget.reserveCall(store.recording.memory, plan.estimatedCallBytes)) {
1263
1404
  return observeRecordedCallback(
1264
1405
  store.recording,
1265
1406
  recorderStartedAtNs,
@@ -1273,7 +1414,7 @@ var createExecutionRecorder = (options) => {
1273
1414
  const enter = {
1274
1415
  type: "function_enter",
1275
1416
  callId,
1276
- functionId,
1417
+ functionId: plan.functionId,
1277
1418
  timestampMs: elapsedMs(store.recording.startedAtNs),
1278
1419
  args: Array.from(args, (argument) => captureValue(store.recording, argument)),
1279
1420
  ...parentCallId === void 0 ? {} : { parentCallId }
@@ -1294,6 +1435,7 @@ var createExecutionRecorder = (options) => {
1294
1435
  durationMs: elapsedMs(startedAtNs),
1295
1436
  returnValue: captureValue(store.recording, value)
1296
1437
  });
1438
+ stats.functionCallsCaptured++;
1297
1439
  },
1298
1440
  (error) => {
1299
1441
  const capturedError = serializedError(store.recording, error, callId);
@@ -1304,16 +1446,23 @@ var createExecutionRecorder = (options) => {
1304
1446
  durationMs: elapsedMs(startedAtNs),
1305
1447
  error: capturedError.value
1306
1448
  });
1449
+ stats.functionCallsCaptured++;
1307
1450
  capturedError.commit();
1308
1451
  rememberError(error, store.recording);
1309
1452
  }
1310
1453
  )
1311
1454
  );
1312
1455
  };
1456
+ const runFunction = (input, args, callback) => runPreparedFunction(preparePlan(normalizeFunctionDefinition(input)), args, callback);
1457
+ const runFunctionPlans = (plans, planIndex, args, callback) => {
1458
+ const plan = preparedPlansFor(plans)[planIndex];
1459
+ return plan ? runPreparedFunction(plan, args, callback) : callback();
1460
+ };
1313
1461
  function trace(input, fn) {
1314
1462
  const definition = typeof input === "string" ? manualFunctionDefinition(input) : { ...input, functionId: runtimeFunctionId2(input) };
1463
+ const plan = preparePlan(definition);
1315
1464
  return function traced(...args) {
1316
- return runFunction(definition, args, () => fn.apply(this, args));
1465
+ return runPreparedFunction(plan, args, () => fn.apply(this, args));
1317
1466
  };
1318
1467
  }
1319
1468
  return {
@@ -1321,11 +1470,51 @@ var createExecutionRecorder = (options) => {
1321
1470
  run,
1322
1471
  getErrorState,
1323
1472
  runFunction,
1473
+ runFunctionPlans,
1474
+ runPreparedFunction,
1324
1475
  trace,
1325
1476
  getStats: () => ({ ...stats, ...memoryBudget.getStats() })
1326
1477
  };
1327
1478
  };
1328
1479
 
1480
+ // src/execution-recorder/automatic-runtime.ts
1481
+ var AUTOMATIC_RUNTIME_SYMBOL = "rasputin.execution.runtime.v2";
1482
+ var runtimeSymbol = Symbol.for(AUTOMATIC_RUNTIME_SYMBOL);
1483
+ var createRegistry = () => {
1484
+ const runtimes = [];
1485
+ return {
1486
+ __rasputinRuntimeRegistry: true,
1487
+ runtimes,
1488
+ register(plans) {
1489
+ return (planIndex, args, callback) => {
1490
+ const runtime = runtimes.at(-1);
1491
+ return runtime ? runtime.run(plans, planIndex, args, callback) : callback();
1492
+ };
1493
+ }
1494
+ };
1495
+ };
1496
+ var getAutomaticRuntimeRegistry = () => {
1497
+ const existing = Reflect.get(globalThis, runtimeSymbol);
1498
+ if (existing && Array.isArray(existing.runtimes) && typeof existing.register === "function") {
1499
+ existing.__rasputinRuntimeRegistry = true;
1500
+ return existing;
1501
+ }
1502
+ const registry = createRegistry();
1503
+ Reflect.set(globalThis, runtimeSymbol, registry);
1504
+ return registry;
1505
+ };
1506
+ var installAutomaticExecutionRuntime = (execution) => {
1507
+ const registry = getAutomaticRuntimeRegistry();
1508
+ const runtime = {
1509
+ run: (plans, planIndex, args, callback) => execution.runFunctionPlans(plans, planIndex, args, callback)
1510
+ };
1511
+ registry.runtimes.push(runtime);
1512
+ return () => {
1513
+ const index = registry.runtimes.lastIndexOf(runtime);
1514
+ if (index >= 0) registry.runtimes.splice(index, 1);
1515
+ };
1516
+ };
1517
+
1329
1518
  // src/install-global-handlers.ts
1330
1519
  var removeProcessListener = process.off.bind(process);
1331
1520
  var installGlobalHandlers = (client, options = {}) => {
@@ -1375,6 +1564,7 @@ var installGlobalHandlers = (client, options = {}) => {
1375
1564
  };
1376
1565
 
1377
1566
  // src/rasputin-init.ts
1567
+ import { join } from "node:path";
1378
1568
  import {
1379
1569
  createClient,
1380
1570
  isClientEnabled
@@ -1382,7 +1572,7 @@ import {
1382
1572
 
1383
1573
  // src/sdk-meta.ts
1384
1574
  var SDK_NAME = "@rasputin-ai/node";
1385
- var SDK_VERSION = "0.4.0";
1575
+ var SDK_VERSION = "0.5.0-alpha.1";
1386
1576
 
1387
1577
  // src/rasputin-init.ts
1388
1578
  var withExecution = (client, execution, installRuntime, manifest) => {
@@ -1423,6 +1613,9 @@ var RasputinInit = (options) => {
1423
1613
  ...options.executionRecorder,
1424
1614
  enabled: recorderEnabled
1425
1615
  });
1616
+ loadInstrumentationManifestIfPresent(
1617
+ options.instrumentationManifestPath ?? process.env.RASPUTIN_INSTRUMENTATION_MANIFEST ?? join(process.cwd(), ".rasputin", "instrumentation-manifest.json")
1618
+ );
1426
1619
  const wrappedClient = withExecution(client, execution, recorderEnabled, {
1427
1620
  projectApiKey: options.projectApiKey,
1428
1621
  release: options.release,